//
//  Bismillah ar-Rahmaan ar-Raheem
//
//  Easylogging++ v9.95.0
//  Cross-platform logging library for C++ applications
//
//  Copyright (c) 2017 muflihun.com
//
//  This library is released under the MIT Licence.
//  http://labs.muflihun.com/easyloggingpp/licence.php
//
//  https://github.com/muflihun/easyloggingpp
//  https://muflihun.github.io/easyloggingpp
//  http://muflihun.com
//

#include "thundergbm/util/log.h"

#if defined(AUTO_INITIALIZE_EASYLOGGINGPP)
INITIALIZE_EASYLOGGINGPP
#endif

namespace el {

// el::base::utils
    namespace base {
        namespace utils {

/// @brief Aborts application due with user-defined status
            static void abort(int status, const std::string &reason) {
                // Both status and reason params are there for debugging with tools like gdb etc
                ELPP_UNUSED(status);
                ELPP_UNUSED(reason);
#if defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG)
                // Ignore msvc critical error dialog - break instead (on debug mode)
  _asm int 3
#else
                ::abort();
#endif  // defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG)
            }

        } // namespace utils
    } // namespace base

// el

// LevelHelper

    const char *LevelHelper::convertToString(Level level) {
        // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet.
        if (level == Level::Global) return "GLOBAL";
        if (level == Level::Debug) return "DEBUG";
        if (level == Level::Info) return "INFO";
        if (level == Level::Warning) return "WARNING";
        if (level == Level::Error) return "ERROR";
        if (level == Level::Fatal) return "FATAL";
        if (level == Level::Verbose) return "VERBOSE";
        if (level == Level::Trace) return "TRACE";
        return "UNKNOWN";
    }

    struct StringToLevelItem {
        const char *levelString;
        Level level;
    };

    static struct StringToLevelItem stringToLevelMap[] = {
            {"global",  Level::Global},
            {"debug",   Level::Debug},
            {"info",    Level::Info},
            {"warning", Level::Warning},
            {"error",   Level::Error},
            {"fatal",   Level::Fatal},
            {"verbose", Level::Verbose},
            {"trace",   Level::Trace}
    };

    Level LevelHelper::convertFromString(const char *levelStr) {
        for (auto &item : stringToLevelMap) {
            if (base::utils::Str::cStringCaseEq(levelStr, item.levelString)) {
                return item.level;
            }
        }
        return Level::Unknown;
    }

    void LevelHelper::forEachLevel(base::type::EnumType *startIndex, const std::function<bool(void)> &fn) {
        base::type::EnumType lIndexMax = LevelHelper::kMaxValid;
        do {
            if (fn()) {
                break;
            }
            *startIndex = static_cast<base::type::EnumType>(*startIndex << 1);
        } while (*startIndex <= lIndexMax);
    }

// ConfigurationTypeHelper

    const char *ConfigurationTypeHelper::convertToString(ConfigurationType configurationType) {
        // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet.
        if (configurationType == ConfigurationType::Enabled) return "ENABLED";
        if (configurationType == ConfigurationType::Filename) return "FILENAME";
        if (configurationType == ConfigurationType::Format) return "FORMAT";
        if (configurationType == ConfigurationType::ToFile) return "TO_FILE";
        if (configurationType == ConfigurationType::ToStandardOutput) return "TO_STANDARD_OUTPUT";
        if (configurationType == ConfigurationType::SubsecondPrecision) return "SUBSECOND_PRECISION";
        if (configurationType == ConfigurationType::PerformanceTracking) return "PERFORMANCE_TRACKING";
        if (configurationType == ConfigurationType::MaxLogFileSize) return "MAX_LOG_FILE_SIZE";
        if (configurationType == ConfigurationType::LogFlushThreshold) return "LOG_FLUSH_THRESHOLD";
        return "UNKNOWN";
    }

    struct ConfigurationStringToTypeItem {
        const char *configString;
        ConfigurationType configType;
    };

    static struct ConfigurationStringToTypeItem configStringToTypeMap[] = {
            {"enabled",              ConfigurationType::Enabled},
            {"to_file",              ConfigurationType::ToFile},
            {"to_standard_output",   ConfigurationType::ToStandardOutput},
            {"format",               ConfigurationType::Format},
            {"filename",             ConfigurationType::Filename},
            {"subsecond_precision",  ConfigurationType::SubsecondPrecision},
            {"milliseconds_width",   ConfigurationType::MillisecondsWidth},
            {"performance_tracking", ConfigurationType::PerformanceTracking},
            {"max_log_file_size",    ConfigurationType::MaxLogFileSize},
            {"log_flush_threshold",  ConfigurationType::LogFlushThreshold},
    };

    ConfigurationType ConfigurationTypeHelper::convertFromString(const char *configStr) {
        for (auto &item : configStringToTypeMap) {
            if (base::utils::Str::cStringCaseEq(configStr, item.configString)) {
                return item.configType;
            }
        }
        return ConfigurationType::Unknown;
    }

    void
    ConfigurationTypeHelper::forEachConfigType(base::type::EnumType *startIndex, const std::function<bool(void)> &fn) {
        base::type::EnumType cIndexMax = ConfigurationTypeHelper::kMaxValid;
        do {
            if (fn()) {
                break;
            }
            *startIndex = static_cast<base::type::EnumType>(*startIndex << 1);
        } while (*startIndex <= cIndexMax);
    }

// Configuration

    Configuration::Configuration(const Configuration &c) :
            m_level(c.m_level),
            m_configurationType(c.m_configurationType),
            m_value(c.m_value) {
    }

    Configuration &Configuration::operator=(const Configuration &c) {
        if (&c != this) {
            m_level = c.m_level;
            m_configurationType = c.m_configurationType;
            m_value = c.m_value;
        }
        return *this;
    }

/// @brief Full constructor used to sets value of configuration
    Configuration::Configuration(Level level, ConfigurationType configurationType, const std::string &value) :
            m_level(level),
            m_configurationType(configurationType),
            m_value(value) {
    }

    void Configuration::log(el::base::type::ostream_t &os) const {
        os << LevelHelper::convertToString(m_level)
           << ELPP_LITERAL(" ") << ConfigurationTypeHelper::convertToString(m_configurationType)
           << ELPP_LITERAL(" = ") << m_value.c_str();
    }

/// @brief Used to find configuration from configuration (pointers) repository. Avoid using it.
    Configuration::Predicate::Predicate(Level level, ConfigurationType configurationType) :
            m_level(level),
            m_configurationType(configurationType) {
    }

    bool Configuration::Predicate::operator()(const Configuration *conf) const {
        return ((conf != nullptr) && (conf->level() == m_level) && (conf->configurationType() == m_configurationType));
    }

// Configurations

    Configurations::Configurations(void) :
            m_configurationFile(std::string()),
            m_isFromFile(false) {
    }

    Configurations::Configurations(const std::string &configurationFile, bool useDefaultsForRemaining,
                                   Configurations *base) :
            m_configurationFile(configurationFile),
            m_isFromFile(false) {
        parseFromFile(configurationFile, base);
        if (useDefaultsForRemaining) {
            setRemainingToDefault();
        }
    }

    bool Configurations::parseFromFile(const std::string &configurationFile, Configurations *base) {
        // We initial assertion with true because if we have assertion diabled, we want to pass this
        // check and if assertion is enabled we will have values re-assigned any way.
        bool assertionPassed = true;
        ELPP_ASSERT((assertionPassed = base::utils::File::pathExists(configurationFile.c_str(), true)) == true,
                    "Configuration file [" << configurationFile << "] does not exist!");
        if (!assertionPassed) {
            return false;
        }
        bool success = Parser::parseFromFile(configurationFile, this, base);
        m_isFromFile = success;
        return success;
    }

    bool Configurations::parseFromText(const std::string &configurationsString, Configurations *base) {
        bool success = Parser::parseFromText(configurationsString, this, base);
        if (success) {
            m_isFromFile = false;
        }
        return success;
    }

    void Configurations::setFromBase(Configurations *base) {
        if (base == nullptr || base == this) {
            return;
        }
        base::threading::ScopedLock scopedLock(base->lock());
        for (Configuration *&conf : base->list()) {
            set(conf);
        }
    }

    bool Configurations::hasConfiguration(ConfigurationType configurationType) {
        base::type::EnumType lIndex = LevelHelper::kMinValid;
        bool result = false;
        LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
            if (hasConfiguration(LevelHelper::castFromInt(lIndex), configurationType)) {
                result = true;
            }
            return result;
        });
        return result;
    }

    bool Configurations::hasConfiguration(Level level, ConfigurationType configurationType) {
        base::threading::ScopedLock scopedLock(lock());
#if ELPP_COMPILER_INTEL
        // We cant specify template types here, Intel C++ throws compilation error
  // "error: type name is not allowed"
  return RegistryWithPred::get(level, configurationType) != nullptr;
#else
        return RegistryWithPred<Configuration, Configuration::Predicate>::get(level, configurationType) != nullptr;
#endif  // ELPP_COMPILER_INTEL
    }

    void Configurations::set(Level level, ConfigurationType configurationType, const std::string &value) {
        base::threading::ScopedLock scopedLock(lock());
        unsafeSet(level, configurationType, value);  // This is not unsafe anymore as we have locked mutex
        if (level == Level::Global) {
            unsafeSetGlobally(configurationType, value, false);  // Again this is not unsafe either
        }
    }

    void Configurations::set(Configuration *conf) {
        if (conf == nullptr) {
            return;
        }
        set(conf->level(), conf->configurationType(), conf->value());
    }

    void Configurations::setToDefault(void) {
        setGlobally(ConfigurationType::Enabled, std::string("true"), true);
#if !defined(ELPP_NO_DEFAULT_LOG_FILE)
        setGlobally(ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile), true);
#else
        ELPP_UNUSED(base::consts::kDefaultLogFile);
#endif  // !defined(ELPP_NO_DEFAULT_LOG_FILE)
#if defined(ELPP_NO_LOG_TO_FILE)
        setGlobally(ConfigurationType::ToFile, std::string("false"), true);
#else
        setGlobally(ConfigurationType::ToFile, std::string("true"), true);
#endif // defined(ELPP_NO_LOG_TO_FILE)
        setGlobally(ConfigurationType::ToStandardOutput, std::string("true"), true);
        setGlobally(ConfigurationType::SubsecondPrecision, std::string("3"), true);
        setGlobally(ConfigurationType::PerformanceTracking, std::string("true"), true);
        setGlobally(ConfigurationType::MaxLogFileSize, std::string("0"), true);
        setGlobally(ConfigurationType::LogFlushThreshold, std::string("0"), true);

        setGlobally(ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"), true);
        set(Level::Debug, ConfigurationType::Format,
            std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg"));
        // INFO and WARNING are set to default by Level::Global
        set(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
        set(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
        set(Level::Verbose, ConfigurationType::Format, std::string("%datetime %level-%vlevel [%logger] %msg"));
        set(Level::Trace, ConfigurationType::Format, std::string("%datetime %level [%logger] [%func] [%loc] %msg"));
    }

    void Configurations::setRemainingToDefault(void) {
        base::threading::ScopedLock scopedLock(lock());
#if defined(ELPP_NO_LOG_TO_FILE)
        unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("false"));
#else
        unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("true"));
#endif // defined(ELPP_NO_LOG_TO_FILE)
#if !defined(ELPP_NO_DEFAULT_LOG_FILE)
        unsafeSetIfNotExist(Level::Global, ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile));
#endif  // !defined(ELPP_NO_DEFAULT_LOG_FILE)
        unsafeSetIfNotExist(Level::Global, ConfigurationType::ToStandardOutput, std::string("true"));
        unsafeSetIfNotExist(Level::Global, ConfigurationType::SubsecondPrecision, std::string("3"));
        unsafeSetIfNotExist(Level::Global, ConfigurationType::PerformanceTracking, std::string("true"));
        unsafeSetIfNotExist(Level::Global, ConfigurationType::MaxLogFileSize, std::string("0"));
        unsafeSetIfNotExist(Level::Global, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
        unsafeSetIfNotExist(Level::Debug, ConfigurationType::Format,
                            std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg"));
        // INFO and WARNING are set to default by Level::Global
        unsafeSetIfNotExist(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
        unsafeSetIfNotExist(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
        unsafeSetIfNotExist(Level::Verbose, ConfigurationType::Format,
                            std::string("%datetime %level-%vlevel [%logger] %msg"));
        unsafeSetIfNotExist(Level::Trace, ConfigurationType::Format,
                            std::string("%datetime %level [%logger] [%func] [%loc] %msg"));
    }

    bool Configurations::Parser::parseFromFile(const std::string &configurationFile, Configurations *sender,
                                               Configurations *base) {
        sender->setFromBase(base);
        std::ifstream fileStream_(configurationFile.c_str(), std::ifstream::in);
        ELPP_ASSERT(fileStream_.is_open(),
                    "Unable to open configuration file [" << configurationFile << "] for parsing.");
        bool parsedSuccessfully = false;
        std::string line = std::string();
        Level currLevel = Level::Unknown;
        std::string currConfigStr = std::string();
        std::string currLevelStr = std::string();
        while (fileStream_.good()) {
            std::getline(fileStream_, line);
            parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender);
            ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line);
        }
        return parsedSuccessfully;
    }

    bool Configurations::Parser::parseFromText(const std::string &configurationsString, Configurations *sender,
                                               Configurations *base) {
        sender->setFromBase(base);
        bool parsedSuccessfully = false;
        std::stringstream ss(configurationsString);
        std::string line = std::string();
        Level currLevel = Level::Unknown;
        std::string currConfigStr = std::string();
        std::string currLevelStr = std::string();
        while (std::getline(ss, line)) {
            parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender);
            ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line);
        }
        return parsedSuccessfully;
    }

    void Configurations::Parser::ignoreComments(std::string *line) {
        std::size_t foundAt = 0;
        std::size_t quotesStart = line->find("\"");
        std::size_t quotesEnd = std::string::npos;
        if (quotesStart != std::string::npos) {
            quotesEnd = line->find("\"", quotesStart + 1);
            while (quotesEnd != std::string::npos && line->at(quotesEnd - 1) == '\\') {
                // Do not erase slash yet - we will erase it in parseLine(..) while loop
                quotesEnd = line->find("\"", quotesEnd + 2);
            }
        }
        if ((foundAt = line->find(base::consts::kConfigurationComment)) != std::string::npos) {
            if (foundAt < quotesEnd) {
                foundAt = line->find(base::consts::kConfigurationComment, quotesEnd + 1);
            }
            *line = line->substr(0, foundAt);
        }
    }

    bool Configurations::Parser::isLevel(const std::string &line) {
        return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLevel));
    }

    bool Configurations::Parser::isComment(const std::string &line) {
        return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationComment));
    }

    bool Configurations::Parser::isConfig(const std::string &line) {
        std::size_t assignment = line.find('=');
        return line != "" &&
               ((line[0] >= 'A' && line[0] <= 'Z') || (line[0] >= 'a' && line[0] <= 'z')) &&
               (assignment != std::string::npos) &&
               (line.size() > assignment);
    }

    bool Configurations::Parser::parseLine(std::string *line, std::string *currConfigStr, std::string *currLevelStr,
                                           Level *currLevel,
                                           Configurations *conf) {
        ConfigurationType currConfig = ConfigurationType::Unknown;
        std::string currValue = std::string();
        *line = base::utils::Str::trim(*line);
        if (isComment(*line)) return true;
        ignoreComments(line);
        *line = base::utils::Str::trim(*line);
        if (line->empty()) {
            // Comment ignored
            return true;
        }
        if (isLevel(*line)) {
            if (line->size() <= 2) {
                return true;
            }
            *currLevelStr = line->substr(1, line->size() - 2);
            *currLevelStr = base::utils::Str::toUpper(*currLevelStr);
            *currLevelStr = base::utils::Str::trim(*currLevelStr);
            *currLevel = LevelHelper::convertFromString(currLevelStr->c_str());
            return true;
        }
        if (isConfig(*line)) {
            std::size_t assignment = line->find('=');
            *currConfigStr = line->substr(0, assignment);
            *currConfigStr = base::utils::Str::toUpper(*currConfigStr);
            *currConfigStr = base::utils::Str::trim(*currConfigStr);
            currConfig = ConfigurationTypeHelper::convertFromString(currConfigStr->c_str());
            currValue = line->substr(assignment + 1);
            currValue = base::utils::Str::trim(currValue);
            std::size_t quotesStart = currValue.find("\"", 0);
            std::size_t quotesEnd = std::string::npos;
            if (quotesStart != std::string::npos) {
                quotesEnd = currValue.find("\"", quotesStart + 1);
                while (quotesEnd != std::string::npos && currValue.at(quotesEnd - 1) == '\\') {
                    currValue = currValue.erase(quotesEnd - 1, 1);
                    quotesEnd = currValue.find("\"", quotesEnd + 2);
                }
            }
            if (quotesStart != std::string::npos && quotesEnd != std::string::npos) {
                // Quote provided - check and strip if valid
                ELPP_ASSERT((quotesStart < quotesEnd), "Configuration error - No ending quote found in ["
                        << currConfigStr << "]");
                ELPP_ASSERT((quotesStart + 1 != quotesEnd), "Empty configuration value for [" << currConfigStr << "]");
                if ((quotesStart != quotesEnd) && (quotesStart + 1 != quotesEnd)) {
                    // Explicit check in case if assertion is disabled
                    currValue = currValue.substr(quotesStart + 1, quotesEnd - 1);
                }
            }
        }
        ELPP_ASSERT(*currLevel != Level::Unknown, "Unrecognized severity level [" << *currLevelStr << "]");
        ELPP_ASSERT(currConfig != ConfigurationType::Unknown, "Unrecognized configuration [" << *currConfigStr << "]");
        if (*currLevel == Level::Unknown || currConfig == ConfigurationType::Unknown) {
            return false;  // unrecognizable level or config
        }
        conf->set(*currLevel, currConfig, currValue);
        return true;
    }

    void
    Configurations::unsafeSetIfNotExist(Level level, ConfigurationType configurationType, const std::string &value) {
        Configuration *conf = RegistryWithPred<Configuration, Configuration::Predicate>::get(level, configurationType);
        if (conf == nullptr) {
            unsafeSet(level, configurationType, value);
        }
    }

    void Configurations::unsafeSet(Level level, ConfigurationType configurationType, const std::string &value) {
        Configuration *conf = RegistryWithPred<Configuration, Configuration::Predicate>::get(level, configurationType);
        if (conf == nullptr) {
            registerNew(new Configuration(level, configurationType, value));
        } else {
            conf->setValue(value);
        }
        if (level == Level::Global) {
            unsafeSetGlobally(configurationType, value, false);
        }
    }

    void Configurations::setGlobally(ConfigurationType configurationType, const std::string &value,
                                     bool includeGlobalLevel) {
        if (includeGlobalLevel) {
            set(Level::Global, configurationType, value);
        }
        base::type::EnumType lIndex = LevelHelper::kMinValid;
        LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
            set(LevelHelper::castFromInt(lIndex), configurationType, value);
            return false;  // Do not break lambda function yet as we need to set all levels regardless
        });
    }

    void Configurations::unsafeSetGlobally(ConfigurationType configurationType, const std::string &value,
                                           bool includeGlobalLevel) {
        if (includeGlobalLevel) {
            unsafeSet(Level::Global, configurationType, value);
        }
        base::type::EnumType lIndex = LevelHelper::kMinValid;
        LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
            unsafeSet(LevelHelper::castFromInt(lIndex), configurationType, value);
            return false;  // Do not break lambda function yet as we need to set all levels regardless
        });
    }

// LogBuilder

    void LogBuilder::convertToColoredOutput(base::type::string_t *logLine, Level level) {
        if (!m_termSupportsColor) return;
        const base::type::char_t *resetColor = ELPP_LITERAL("\x1b[0m");
        if (level == Level::Error || level == Level::Fatal)
            *logLine = ELPP_LITERAL("\x1b[31m") + *logLine + resetColor;
        else if (level == Level::Warning)
            *logLine = ELPP_LITERAL("\x1b[33m") + *logLine + resetColor;
        else if (level == Level::Debug)
            *logLine = ELPP_LITERAL("\x1b[32m") + *logLine + resetColor;
        else if (level == Level::Info)
            *logLine = ELPP_LITERAL("\x1b[36m") + *logLine + resetColor;
        else if (level == Level::Trace)
            *logLine = ELPP_LITERAL("\x1b[35m") + *logLine + resetColor;
    }

// Logger

    Logger::Logger(const std::string &id, base::LogStreamsReferenceMap *logStreamsReference) :
            m_id(id),
            m_typedConfigurations(nullptr),
            m_parentApplicationName(std::string()),
            m_isConfigured(false),
            m_logStreamsReference(logStreamsReference) {
        initUnflushedCount();
    }

    Logger::Logger(const std::string &id, const Configurations &configurations,
                   base::LogStreamsReferenceMap *logStreamsReference) :
            m_id(id),
            m_typedConfigurations(nullptr),
            m_parentApplicationName(std::string()),
            m_isConfigured(false),
            m_logStreamsReference(logStreamsReference) {
        initUnflushedCount();
        configure(configurations);
    }

    Logger::Logger(const Logger &logger) {
        base::utils::safeDelete(m_typedConfigurations);
        m_id = logger.m_id;
        m_typedConfigurations = logger.m_typedConfigurations;
        m_parentApplicationName = logger.m_parentApplicationName;
        m_isConfigured = logger.m_isConfigured;
        m_configurations = logger.m_configurations;
        m_unflushedCount = logger.m_unflushedCount;
        m_logStreamsReference = logger.m_logStreamsReference;
    }

    Logger &Logger::operator=(const Logger &logger) {
        if (&logger != this) {
            base::utils::safeDelete(m_typedConfigurations);
            m_id = logger.m_id;
            m_typedConfigurations = logger.m_typedConfigurations;
            m_parentApplicationName = logger.m_parentApplicationName;
            m_isConfigured = logger.m_isConfigured;
            m_configurations = logger.m_configurations;
            m_unflushedCount = logger.m_unflushedCount;
            m_logStreamsReference = logger.m_logStreamsReference;
        }
        return *this;
    }

    void Logger::configure(const Configurations &configurations) {
        m_isConfigured = false;  // we set it to false in case if we fail
        initUnflushedCount();
        if (m_typedConfigurations != nullptr) {
            Configurations *c = const_cast<Configurations *>(m_typedConfigurations->configurations());
            if (c->hasConfiguration(Level::Global, ConfigurationType::Filename)) {
                // This check is definitely needed for cases like ELPP_NO_DEFAULT_LOG_FILE
                flush();
            }
        }
        base::threading::ScopedLock scopedLock(lock());
        if (m_configurations != configurations) {
            m_configurations.setFromBase(const_cast<Configurations *>(&configurations));
        }
        base::utils::safeDelete(m_typedConfigurations);
        m_typedConfigurations = new base::TypedConfigurations(&m_configurations, m_logStreamsReference);
        resolveLoggerFormatSpec();
        m_isConfigured = true;
    }

    void Logger::reconfigure(void) {
        ELPP_INTERNAL_INFO(1, "Reconfiguring logger [" << m_id << "]");
        configure(m_configurations);
    }

    bool Logger::isValidId(const std::string &id) {
        for (std::string::const_iterator it = id.begin(); it != id.end(); ++it) {
            if (!base::utils::Str::contains(base::consts::kValidLoggerIdSymbols, *it)) {
                return false;
            }
        }
        return true;
    }

    void Logger::flush(void) {
        ELPP_INTERNAL_INFO(3, "Flushing logger [" << m_id << "] all levels");
        base::threading::ScopedLock scopedLock(lock());
        base::type::EnumType lIndex = LevelHelper::kMinValid;
        LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
            flush(LevelHelper::castFromInt(lIndex), nullptr);
            return false;
        });
    }

    void Logger::flush(Level level, base::type::fstream_t *fs) {
        if (fs == nullptr && m_typedConfigurations->toFile(level)) {
            fs = m_typedConfigurations->fileStream(level);
        }
        if (fs != nullptr) {
            fs->flush();
            std::map<Level, unsigned int>::iterator iter = m_unflushedCount.find(level);
            if (iter != m_unflushedCount.end()) {
                iter->second = 0;
            }
        }
    }

    void Logger::initUnflushedCount(void) {
        m_unflushedCount.clear();
        base::type::EnumType lIndex = LevelHelper::kMinValid;
        LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
            m_unflushedCount.insert(std::make_pair(LevelHelper::castFromInt(lIndex), 0));
            return false;
        });
    }

    void Logger::resolveLoggerFormatSpec(void) const {
        base::type::EnumType lIndex = LevelHelper::kMinValid;
        LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
            base::LogFormat *logFormat =
                    const_cast<base::LogFormat *>(&m_typedConfigurations->logFormat(LevelHelper::castFromInt(lIndex)));
            base::utils::Str::replaceFirstWithEscape(logFormat->m_format, base::consts::kLoggerIdFormatSpecifier, m_id);
            return false;
        });
    }

// el::base
    namespace base {

// el::base::utils
        namespace utils {

// File

            base::type::fstream_t *File::newFileStream(const std::string &filename) {
                base::type::fstream_t *fs = new base::type::fstream_t(filename.c_str(),
                                                                      base::type::fstream_t::out
                                                                      #if !defined(ELPP_FRESH_LOG_FILE)
                                                                      | base::type::fstream_t::app
#endif
                );
#if defined(ELPP_UNICODE)
                std::locale elppUnicodeLocale("");
#  if ELPP_OS_WINDOWS
  std::locale elppUnicodeLocaleWindows(elppUnicodeLocale, new std::codecvt_utf8_utf16<wchar_t>);
  elppUnicodeLocale = elppUnicodeLocaleWindows;
#  endif // ELPP_OS_WINDOWS
  fs->imbue(elppUnicodeLocale);
#endif  // defined(ELPP_UNICODE)
                if (fs->is_open()) {
                    fs->flush();
                } else {
                    base::utils::safeDelete(fs);
                    ELPP_INTERNAL_ERROR("Bad file [" << filename << "]", true);
                }
                return fs;
            }

            std::size_t File::getSizeOfFile(base::type::fstream_t *fs) {
                if (fs == nullptr) {
                    return 0;
                }
                std::streampos currPos = fs->tellg();
                fs->seekg(0, fs->end);
                std::size_t size = static_cast<std::size_t>(fs->tellg());
                fs->seekg(currPos);
                return size;
            }

            bool File::pathExists(const char *path, bool considerFile) {
                if (path == nullptr) {
                    return false;
                }
#if ELPP_OS_UNIX
                ELPP_UNUSED(considerFile);
                struct stat st;
                return (stat(path, &st) == 0);
#elif ELPP_OS_WINDOWS
                DWORD fileType = GetFileAttributesA(path);
  if (fileType == INVALID_FILE_ATTRIBUTES) {
    return false;
  }
  return considerFile ? true : ((fileType & FILE_ATTRIBUTE_DIRECTORY) == 0 ? false : true);
#endif  // ELPP_OS_UNIX
            }

            bool File::createPath(const std::string &path) {
                if (path.empty()) {
                    return false;
                }
                if (base::utils::File::pathExists(path.c_str())) {
                    return true;
                }
                int status = -1;

                char *currPath = const_cast<char *>(path.c_str());
                std::string builtPath = std::string();
#if ELPP_OS_UNIX
                if (path[0] == '/') {
                    builtPath = "/";
                }
                currPath = STRTOK(currPath, base::consts::kFilePathSeperator, 0);
#elif ELPP_OS_WINDOWS
                // Use secure functions API
  char* nextTok_ = nullptr;
  currPath = STRTOK(currPath, base::consts::kFilePathSeperator, &nextTok_);
  ELPP_UNUSED(nextTok_);
#endif  // ELPP_OS_UNIX
                while (currPath != nullptr) {
                    builtPath.append(currPath);
                    builtPath.append(base::consts::kFilePathSeperator);
#if ELPP_OS_UNIX
                    status = mkdir(builtPath.c_str(), ELPP_LOG_PERMS);
                    currPath = STRTOK(nullptr, base::consts::kFilePathSeperator, 0);
#elif ELPP_OS_WINDOWS
                    status = _mkdir(builtPath.c_str());
    currPath = STRTOK(nullptr, base::consts::kFilePathSeperator, &nextTok_);
#endif  // ELPP_OS_UNIX
                }
                if (status == -1) {
                    ELPP_INTERNAL_ERROR("Error while creating path [" << path << "]", true);
                    return false;
                }
                return true;
            }

            std::string File::extractPathFromFilename(const std::string &fullPath, const char *separator) {
                if ((fullPath == "") || (fullPath.find(separator) == std::string::npos)) {
                    return fullPath;
                }
                std::size_t lastSlashAt = fullPath.find_last_of(separator);
                if (lastSlashAt == 0) {
                    return std::string(separator);
                }
                return fullPath.substr(0, lastSlashAt + 1);
            }

            void File::buildStrippedFilename(const char *filename, char buff[], std::size_t limit) {
                std::size_t sizeOfFilename = strlen(filename);
                if (sizeOfFilename >= limit) {
                    filename += (sizeOfFilename - limit);
                    if (filename[0] != '.' && filename[1] != '.') {  // prepend if not already
                        filename += 3;  // 3 = '..'
                        STRCAT(buff, "..", limit);
                    }
                }
                STRCAT(buff, filename, limit);
            }

            void File::buildBaseFilename(const std::string &fullPath, char buff[], std::size_t limit,
                                         const char *separator) {
                const char *filename = fullPath.c_str();
                std::size_t lastSlashAt = fullPath.find_last_of(separator);
                filename += lastSlashAt ? lastSlashAt + 1 : 0;
                std::size_t sizeOfFilename = strlen(filename);
                if (sizeOfFilename >= limit) {
                    filename += (sizeOfFilename - limit);
                    if (filename[0] != '.' && filename[1] != '.') {  // prepend if not already
                        filename += 3;  // 3 = '..'
                        STRCAT(buff, "..", limit);
                    }
                }
                STRCAT(buff, filename, limit);
            }

// Str

            bool Str::wildCardMatch(const char *str, const char *pattern) {
                while (*pattern) {
                    switch (*pattern) {
                        case '?':
                            if (!*str)
                                return false;
                            ++str;
                            ++pattern;
                            break;
                        case '*':
                            if (wildCardMatch(str, pattern + 1))
                                return true;
                            if (*str && wildCardMatch(str + 1, pattern))
                                return true;
                            return false;
                        default:
                            if (*str++ != *pattern++)
                                return false;
                            break;
                    }
                }
                return !*str && !*pattern;
            }

            std::string &Str::ltrim(std::string &str) {
                str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](char c) {
                    return !std::isspace(c);
                }));
                return str;
            }

            std::string &Str::rtrim(std::string &str) {
                str.erase(std::find_if(str.rbegin(), str.rend(), [](char c) {
                    return !std::isspace(c);
                }).base(), str.end());
                return str;
            }

            std::string &Str::trim(std::string &str) {
                return ltrim(rtrim(str));
            }

            bool Str::startsWith(const std::string &str, const std::string &start) {
                return (str.length() >= start.length()) && (str.compare(0, start.length(), start) == 0);
            }

            bool Str::endsWith(const std::string &str, const std::string &end) {
                return (str.length() >= end.length()) &&
                       (str.compare(str.length() - end.length(), end.length(), end) == 0);
            }

            std::string &Str::replaceAll(std::string &str, char replaceWhat, char replaceWith) {
                std::replace(str.begin(), str.end(), replaceWhat, replaceWith);
                return str;
            }

            std::string &Str::replaceAll(std::string &str, const std::string &replaceWhat,
                                         const std::string &replaceWith) {
                if (replaceWhat == replaceWith)
                    return str;
                std::size_t foundAt = std::string::npos;
                while ((foundAt = str.find(replaceWhat, foundAt + 1)) != std::string::npos) {
                    str.replace(foundAt, replaceWhat.length(), replaceWith);
                }
                return str;
            }

            void Str::replaceFirstWithEscape(base::type::string_t &str, const base::type::string_t &replaceWhat,
                                             const base::type::string_t &replaceWith) {
                std::size_t foundAt = base::type::string_t::npos;
                while ((foundAt = str.find(replaceWhat, foundAt + 1)) != base::type::string_t::npos) {
                    if (foundAt > 0 && str[foundAt - 1] == base::consts::kFormatSpecifierChar) {
                        str.erase(foundAt > 0 ? foundAt - 1 : 0, 1);
                        ++foundAt;
                    } else {
                        str.replace(foundAt, replaceWhat.length(), replaceWith);
                        return;
                    }
                }
            }

#if defined(ELPP_UNICODE)
            void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat,
                                 const std::string& replaceWith) {
  replaceFirstWithEscape(str, replaceWhat, base::type::string_t(replaceWith.begin(), replaceWith.end()));
}
#endif  // defined(ELPP_UNICODE)

            std::string &Str::toUpper(std::string &str) {
                std::transform(str.begin(), str.end(), str.begin(),
                               [](char c) {
                                   return static_cast<char>(::toupper(c));
                               });
                return str;
            }

            bool Str::cStringEq(const char *s1, const char *s2) {
                if (s1 == nullptr && s2 == nullptr) return true;
                if (s1 == nullptr || s2 == nullptr) return false;
                return strcmp(s1, s2) == 0;
            }

            bool Str::cStringCaseEq(const char *s1, const char *s2) {
                if (s1 == nullptr && s2 == nullptr) return true;
                if (s1 == nullptr || s2 == nullptr) return false;

                // With thanks to cygwin for this code
                int d = 0;

                while (true) {
                    const int c1 = toupper(*s1++);
                    const int c2 = toupper(*s2++);

                    if (((d = c1 - c2) != 0) || (c2 == '\0')) {
                        break;
                    }
                }

                return d == 0;
            }

            bool Str::contains(const char *str, char c) {
                for (; *str; ++str) {
                    if (*str == c)
                        return true;
                }
                return false;
            }

            char *Str::convertAndAddToBuff(std::size_t n, int len, char *buf, const char *bufLim, bool zeroPadded) {
                char localBuff[10] = "";
                char *p = localBuff + sizeof(localBuff) - 2;
                if (n > 0) {
                    for (; n > 0 && p > localBuff && len > 0; n /= 10, --len)
                        *--p = static_cast<char>(n % 10 + '0');
                } else {
                    *--p = '0';
                    --len;
                }
                if (zeroPadded)
                    while (p > localBuff && len-- > 0) *--p = static_cast<char>('0');
                return addToBuff(p, buf, bufLim);
            }

            char *Str::addToBuff(const char *str, char *buf, const char *bufLim) {
                while ((buf < bufLim) && ((*buf = *str++) != '\0'))
                    ++buf;
                return buf;
            }

            char *Str::clearBuff(char buff[], std::size_t lim) {
                STRCPY(buff, "", lim);
                ELPP_UNUSED(lim);  // For *nix we dont have anything using lim in above STRCPY macro
                return buff;
            }

/// @brief Converst wchar* to char*
///        NOTE: Need to free return value after use!
            char *Str::wcharPtrToCharPtr(const wchar_t *line) {
                std::size_t len_ = wcslen(line) + 1;
                char *buff_ = static_cast<char *>(malloc(len_ + 1));
#      if ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS)
                std::wcstombs(buff_, line, len_);
#      elif ELPP_OS_WINDOWS
                std::size_t convCount_ = 0;
  mbstate_t mbState_;
  ::memset(static_cast<void*>(&mbState_), 0, sizeof(mbState_));
  wcsrtombs_s(&convCount_, buff_, len_, &line, len_, &mbState_);
#      endif  // ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS)
                return buff_;
            }

// OS

#if ELPP_OS_WINDOWS
            /// @brief Gets environment variables for Windows based OS.
///        We are not using <code>getenv(const char*)</code> because of CRT deprecation
/// @param varname Variable name to get environment variable value for
/// @return If variable exist the value of it otherwise nullptr
const char* OS::getWindowsEnvironmentVariable(const char* varname) {
  const DWORD bufferLen = 50;
  static char buffer[bufferLen];
  if (GetEnvironmentVariableA(varname, buffer, bufferLen)) {
    return buffer;
  }
  return nullptr;
}
#endif  // ELPP_OS_WINDOWS
#if ELPP_OS_ANDROID
            std::string OS::getProperty(const char* prop) {
  char propVal[PROP_VALUE_MAX + 1];
  int ret = __system_property_get(prop, propVal);
  return ret == 0 ? std::string() : std::string(propVal);
}

std::string OS::getDeviceName(void) {
  std::stringstream ss;
  std::string manufacturer = getProperty("ro.product.manufacturer");
  std::string model = getProperty("ro.product.model");
  if (manufacturer.empty() || model.empty()) {
    return std::string();
  }
  ss << manufacturer << "-" << model;
  return ss.str();
}
#endif  // ELPP_OS_ANDROID

            const std::string OS::getBashOutput(const char *command) {
#if (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN)
                if (command == nullptr) {
                    return std::string();
                }
                FILE *proc = nullptr;
                if ((proc = popen(command, "r")) == nullptr) {
                    ELPP_INTERNAL_ERROR("\nUnable to run command [" << command << "]", true);
                    return std::string();
                }
                char hBuff[4096];
                if (fgets(hBuff, sizeof(hBuff), proc) != nullptr) {
                    pclose(proc);
                    if (hBuff[strlen(hBuff) - 1] == '\n') {
                        hBuff[strlen(hBuff) - 1] = '\0';
                    }
                    return std::string(hBuff);
                } else {
                    pclose(proc);
                }
                return std::string();
#else
                ELPP_UNUSED(command);
                return std::string();
#endif  // (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN)
            }

            std::string OS::getEnvironmentVariable(const char *variableName, const char *defaultVal,
                                                   const char *alternativeBashCommand) {
#if ELPP_OS_UNIX
                const char *val = getenv(variableName);
#elif ELPP_OS_WINDOWS
                const char* val = getWindowsEnvironmentVariable(variableName);
#endif  // ELPP_OS_UNIX
                if ((val == nullptr) || ((strcmp(val, "") == 0))) {
#if ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH)
                    // Try harder on unix-based systems
    std::string valBash = base::utils::OS::getBashOutput(alternativeBashCommand);
    if (valBash.empty()) {
      return std::string(defaultVal);
    } else {
      return valBash;
    }
#elif ELPP_OS_WINDOWS || ELPP_OS_UNIX
                    ELPP_UNUSED(alternativeBashCommand);
                    return std::string(defaultVal);
#endif  // ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH)
                }
                return std::string(val);
            }

            std::string OS::currentUser(void) {
#if ELPP_OS_UNIX && !ELPP_OS_ANDROID
                return getEnvironmentVariable("USER", base::consts::kUnknownUser, "whoami");
#elif ELPP_OS_WINDOWS
                return getEnvironmentVariable("USERNAME", base::consts::kUnknownUser);
#elif ELPP_OS_ANDROID
                ELPP_UNUSED(base::consts::kUnknownUser);
  return std::string("android");
#else
                return std::string();
#endif  // ELPP_OS_UNIX && !ELPP_OS_ANDROID
            }

            std::string OS::currentHost(void) {
#if ELPP_OS_UNIX && !ELPP_OS_ANDROID
                return getEnvironmentVariable("HOSTNAME", base::consts::kUnknownHost, "hostname");
#elif ELPP_OS_WINDOWS
                return getEnvironmentVariable("COMPUTERNAME", base::consts::kUnknownHost);
#elif ELPP_OS_ANDROID
                ELPP_UNUSED(base::consts::kUnknownHost);
  return getDeviceName();
#else
                return std::string();
#endif  // ELPP_OS_UNIX && !ELPP_OS_ANDROID
            }

            bool OS::termSupportsColor(void) {
                std::string term = getEnvironmentVariable("TERM", "");
                return term == "xterm" || term == "xterm-color" || term == "xterm-256color"
                       || term == "screen" || term == "linux" || term == "cygwin"
                       || term == "screen-256color";
            }

// DateTime

            void DateTime::gettimeofday(struct timeval *tv) {
#if ELPP_OS_WINDOWS
                if (tv != nullptr) {
#  if ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS)
    const unsigned __int64 delta_ = 11644473600000000Ui64;
#  else
    const unsigned __int64 delta_ = 11644473600000000ULL;
#  endif  // ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS)
    const double secOffSet = 0.000001;
    const unsigned long usecOffSet = 1000000;
    FILETIME fileTime;
    GetSystemTimeAsFileTime(&fileTime);
    unsigned __int64 present = 0;
    present |= fileTime.dwHighDateTime;
    present = present << 32;
    present |= fileTime.dwLowDateTime;
    present /= 10;  // mic-sec
    // Subtract the difference
    present -= delta_;
    tv->tv_sec = static_cast<long>(present * secOffSet);
    tv->tv_usec = static_cast<long>(present % usecOffSet);
  }
#else
                ::gettimeofday(tv, nullptr);
#endif  // ELPP_OS_WINDOWS
            }

            std::string DateTime::getDateTime(const char *format, const base::SubsecondPrecision *ssPrec) {
                struct timeval currTime;
                gettimeofday(&currTime);
                return timevalToString(currTime, format, ssPrec);
            }

            std::string DateTime::timevalToString(struct timeval tval, const char *format,
                                                  const el::base::SubsecondPrecision *ssPrec) {
                struct ::tm timeInfo;
                buildTimeInfo(&tval, &timeInfo);
                const int kBuffSize = 30;
                char buff_[kBuffSize] = "";
                parseFormat(buff_, kBuffSize, format, &timeInfo,
                            static_cast<std::size_t>(tval.tv_usec / ssPrec->m_offset),
                            ssPrec);
                return std::string(buff_);
            }

            base::type::string_t DateTime::formatTime(unsigned long long time, base::TimestampUnit timestampUnit) {
                base::type::EnumType start = static_cast<base::type::EnumType>(timestampUnit);
                const base::type::char_t *unit = base::consts::kTimeFormats[start].unit;
                for (base::type::EnumType i = start; i < base::consts::kTimeFormatsCount - 1; ++i) {
                    if (time <= base::consts::kTimeFormats[i].value) {
                        break;
                    }
                    if (base::consts::kTimeFormats[i].value == 1000.0f && time / 1000.0f < 1.9f) {
                        break;
                    }
                    time /= static_cast<decltype(time)>(base::consts::kTimeFormats[i].value);
                    unit = base::consts::kTimeFormats[i + 1].unit;
                }
                base::type::stringstream_t ss;
                ss << time << " " << unit;
                return ss.str();
            }

            unsigned long long
            DateTime::getTimeDifference(const struct timeval &endTime, const struct timeval &startTime,
                                        base::TimestampUnit timestampUnit) {
                if (timestampUnit == base::TimestampUnit::Microsecond) {
                    return static_cast<unsigned long long>(
                            static_cast<unsigned long long>(1000000 * endTime.tv_sec + endTime.tv_usec) -
                            static_cast<unsigned long long>(1000000 * startTime.tv_sec + startTime.tv_usec));
                }
                // milliseconds
                auto conv = [](const struct timeval &tim) {
                    return static_cast<unsigned long long>((tim.tv_sec * 1000) + (tim.tv_usec / 1000));
                };
                return static_cast<unsigned long long>(conv(endTime) - conv(startTime));
            }

            struct ::tm *DateTime::buildTimeInfo(struct timeval *currTime, struct ::tm *timeInfo) {
#if ELPP_OS_UNIX
                time_t rawTime = currTime->tv_sec;
                ::elpptime_r(&rawTime, timeInfo);
                return timeInfo;
#else
#  if ELPP_COMPILER_MSVC
                ELPP_UNUSED(currTime);
  time_t t;
#    if defined(_USE_32BIT_TIME_T)
  _time32(&t);
#    else
  _time64(&t);
#    endif
  elpptime_s(timeInfo, &t);
  return timeInfo;
#  else
                // For any other compilers that don't have CRT warnings issue e.g, MinGW or TDM GCC- we use different method
                time_t rawTime = currTime->tv_sec;
                struct tm* tmInf = elpptime(&rawTime);
                *timeInfo = *tmInf;
                return timeInfo;
#  endif  // ELPP_COMPILER_MSVC
#endif  // ELPP_OS_UNIX
            }

            char *DateTime::parseFormat(char *buf, std::size_t bufSz, const char *format, const struct tm *tInfo,
                                        std::size_t msec, const base::SubsecondPrecision *ssPrec) {
                const char *bufLim = buf + bufSz;
                for (; *format; ++format) {
                    if (*format == base::consts::kFormatSpecifierChar) {
                        switch (*++format) {
                            case base::consts::kFormatSpecifierChar:  // Escape
                                break;
                            case '\0':  // End
                                --format;
                                break;
                            case 'd':  // Day
                                buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mday, 2, buf, bufLim);
                                continue;
                            case 'a':  // Day of week (short)
                                buf = base::utils::Str::addToBuff(base::consts::kDaysAbbrev[tInfo->tm_wday], buf,
                                                                  bufLim);
                                continue;
                            case 'A':  // Day of week (long)
                                buf = base::utils::Str::addToBuff(base::consts::kDays[tInfo->tm_wday], buf, bufLim);
                                continue;
                            case 'M':  // month
                                buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mon + 1, 2, buf, bufLim);
                                continue;
                            case 'b':  // month (short)
                                buf = base::utils::Str::addToBuff(base::consts::kMonthsAbbrev[tInfo->tm_mon], buf,
                                                                  bufLim);
                                continue;
                            case 'B':  // month (long)
                                buf = base::utils::Str::addToBuff(base::consts::kMonths[tInfo->tm_mon], buf, bufLim);
                                continue;
                            case 'y':  // year (two digits)
                                buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 2,
                                                                            buf, bufLim);
                                continue;
                            case 'Y':  // year (four digits)
                                buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 4,
                                                                            buf, bufLim);
                                continue;
                            case 'h':  // hour (12-hour)
                                buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour % 12, 2, buf, bufLim);
                                continue;
                            case 'H':  // hour (24-hour)
                                buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour, 2, buf, bufLim);
                                continue;
                            case 'm':  // minute
                                buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_min, 2, buf, bufLim);
                                continue;
                            case 's':  // second
                                buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_sec, 2, buf, bufLim);
                                continue;
                            case 'z':  // subsecond part
                            case 'g':
                                buf = base::utils::Str::convertAndAddToBuff(msec, ssPrec->m_width, buf, bufLim);
                                continue;
                            case 'F':  // AM/PM
                                buf = base::utils::Str::addToBuff(
                                        (tInfo->tm_hour >= 12) ? base::consts::kPm : base::consts::kAm, buf, bufLim);
                                continue;
                            default:
                                continue;
                        }
                    }
                    if (buf == bufLim) break;
                    *buf++ = *format;
                }
                return buf;
            }

// CommandLineArgs

            void CommandLineArgs::setArgs(int argc, char **argv) {
                m_params.clear();
                m_paramsWithValue.clear();
                if (argc == 0 || argv == nullptr) {
                    return;
                }
                m_argc = argc;
                m_argv = argv;
                for (int i = 1; i < m_argc; ++i) {
                    const char *v = (strstr(m_argv[i], "="));
                    if (v != nullptr && strlen(v) > 0) {
                        std::string key = std::string(m_argv[i]);
                        key = key.substr(0, key.find_first_of('='));
                        if (hasParamWithValue(key.c_str())) {
                            ELPP_INTERNAL_INFO(1, "Skipping [" << key << "] arg since it already has value ["
                                                               << getParamValue(key.c_str()) << "]");
                        } else {
                            m_paramsWithValue.insert(std::make_pair(key, std::string(v + 1)));
                        }
                    }
                    if (v == nullptr) {
                        if (hasParam(m_argv[i])) {
                            ELPP_INTERNAL_INFO(1, "Skipping [" << m_argv[i] << "] arg since it already exists");
                        } else {
                            m_params.push_back(std::string(m_argv[i]));
                        }
                    }
                }
            }

            bool CommandLineArgs::hasParamWithValue(const char *paramKey) const {
                return m_paramsWithValue.find(std::string(paramKey)) != m_paramsWithValue.end();
            }

            const char *CommandLineArgs::getParamValue(const char *paramKey) const {
                std::map<std::string, std::string>::const_iterator iter = m_paramsWithValue.find(std::string(paramKey));
                return iter != m_paramsWithValue.end() ? iter->second.c_str() : "";
            }

            bool CommandLineArgs::hasParam(const char *paramKey) const {
                return std::find(m_params.begin(), m_params.end(), std::string(paramKey)) != m_params.end();
            }

            bool CommandLineArgs::empty(void) const {
                return m_params.empty() && m_paramsWithValue.empty();
            }

            std::size_t CommandLineArgs::size(void) const {
                return m_params.size() + m_paramsWithValue.size();
            }

            base::type::ostream_t &operator<<(base::type::ostream_t &os, const CommandLineArgs &c) {
                for (int i = 1; i < c.m_argc; ++i) {
                    os << ELPP_LITERAL("[") << c.m_argv[i] << ELPP_LITERAL("]");
                    if (i < c.m_argc - 1) {
                        os << ELPP_LITERAL(" ");
                    }
                }
                return os;
            }

        } // namespace utils

// el::base::threading
        namespace threading {

#if ELPP_THREADING_ENABLED
#  if ELPP_USE_STD_THREADING
#      if ELPP_ASYNC_LOGGING
            static void msleep(int ms) {
              // Only when async logging enabled - this is because async is strict on compiler
#         if defined(ELPP_NO_SLEEP_FOR)
              usleep(ms * 1000);
#         else
              std::this_thread::sleep_for(std::chrono::milliseconds(ms));
#         endif  // defined(ELPP_NO_SLEEP_FOR)
            }
#      endif  // ELPP_ASYNC_LOGGING
#  endif  // !ELPP_USE_STD_THREADING
#endif  // ELPP_THREADING_ENABLED

        } // namespace threading

// el::base

// SubsecondPrecision

        void SubsecondPrecision::init(int width) {
            if (width < 1 || width > 6) {
                width = base::consts::kDefaultSubsecondPrecision;
            }
            m_width = width;
            switch (m_width) {
                case 3:
                    m_offset = 1000;
                    break;
                case 4:
                    m_offset = 100;
                    break;
                case 5:
                    m_offset = 10;
                    break;
                case 6:
                    m_offset = 1;
                    break;
                default:
                    m_offset = 1000;
                    break;
            }
        }

// LogFormat

        LogFormat::LogFormat(void) :
                m_level(Level::Unknown),
                m_userFormat(base::type::string_t()),
                m_format(base::type::string_t()),
                m_dateTimeFormat(std::string()),
                m_flags(0x0),
                m_currentUser(base::utils::OS::currentUser()),
                m_currentHost(base::utils::OS::currentHost()) {
        }

        LogFormat::LogFormat(Level
        level,
        const base::type::string_t &format
        )
        :

        m_level (level), m_userFormat(format), m_currentUser(base::utils::OS::currentUser()),
        m_currentHost(base::utils::OS::currentHost()) {
            parseFromFormat(m_userFormat);
        }

        LogFormat::LogFormat(const LogFormat &logFormat) :
                m_level(logFormat.m_level),
                m_userFormat(logFormat.m_userFormat),
                m_format(logFormat.m_format),
                m_dateTimeFormat(logFormat.m_dateTimeFormat),
                m_flags(logFormat.m_flags),
                m_currentUser(logFormat.m_currentUser),
                m_currentHost(logFormat.m_currentHost) {
        }

        LogFormat::LogFormat(LogFormat &&logFormat) {
            m_level = std::move(logFormat.m_level);
            m_userFormat = std::move(logFormat.m_userFormat);
            m_format = std::move(logFormat.m_format);
            m_dateTimeFormat = std::move(logFormat.m_dateTimeFormat);
            m_flags = std::move(logFormat.m_flags);
            m_currentUser = std::move(logFormat.m_currentUser);
            m_currentHost = std::move(logFormat.m_currentHost);
        }

        LogFormat &LogFormat::operator=(const LogFormat &logFormat) {
            if (&logFormat != this) {
                m_level = logFormat.m_level;
                m_userFormat = logFormat.m_userFormat;
                m_dateTimeFormat = logFormat.m_dateTimeFormat;
                m_flags = logFormat.m_flags;
                m_currentUser = logFormat.m_currentUser;
                m_currentHost = logFormat.m_currentHost;
            }
            return *this;
        }

        bool LogFormat::operator==(const LogFormat &other) {
            return m_level == other.m_level && m_userFormat == other.m_userFormat && m_format == other.m_format &&
                   m_dateTimeFormat == other.m_dateTimeFormat && m_flags == other.m_flags;
        }

/// @brief Updates format to be used while logging.
/// @param userFormat User provided format
        void LogFormat::parseFromFormat(const base::type::string_t &userFormat) {
            // We make copy because we will be changing the format
            // i.e, removing user provided date format from original format
            // and then storing it.
            base::type::string_t formatCopy = userFormat;
            m_flags = 0x0;
            auto conditionalAddFlag = [&](const base::type::char_t *specifier, base::FormatFlags flag) {
                std::size_t foundAt = base::type::string_t::npos;
                while ((foundAt = formatCopy.find(specifier, foundAt + 1)) != base::type::string_t::npos) {
                    if (foundAt > 0 && formatCopy[foundAt - 1] == base::consts::kFormatSpecifierChar) {
                        if (hasFlag(flag)) {
                            // If we already have flag we remove the escape chars so that '%%' is turned to '%'
                            // even after specifier resolution - this is because we only replaceFirst specifier
                            formatCopy.erase(foundAt > 0 ? foundAt - 1 : 0, 1);
                            ++foundAt;
                        }
                    } else {
                        if (!hasFlag(flag)) addFlag(flag);
                    }
                }
            };
            conditionalAddFlag(base::consts::kAppNameFormatSpecifier, base::FormatFlags::AppName);
            conditionalAddFlag(base::consts::kSeverityLevelFormatSpecifier, base::FormatFlags::Level);
            conditionalAddFlag(base::consts::kSeverityLevelShortFormatSpecifier, base::FormatFlags::LevelShort);
            conditionalAddFlag(base::consts::kLoggerIdFormatSpecifier, base::FormatFlags::LoggerId);
            conditionalAddFlag(base::consts::kThreadIdFormatSpecifier, base::FormatFlags::ThreadId);
            conditionalAddFlag(base::consts::kLogFileFormatSpecifier, base::FormatFlags::File);
            conditionalAddFlag(base::consts::kLogFileBaseFormatSpecifier, base::FormatFlags::FileBase);
            conditionalAddFlag(base::consts::kLogLineFormatSpecifier, base::FormatFlags::Line);
            conditionalAddFlag(base::consts::kLogLocationFormatSpecifier, base::FormatFlags::Location);
            conditionalAddFlag(base::consts::kLogFunctionFormatSpecifier, base::FormatFlags::Function);
            conditionalAddFlag(base::consts::kCurrentUserFormatSpecifier, base::FormatFlags::User);
            conditionalAddFlag(base::consts::kCurrentHostFormatSpecifier, base::FormatFlags::Host);
            conditionalAddFlag(base::consts::kMessageFormatSpecifier, base::FormatFlags::LogMessage);
            conditionalAddFlag(base::consts::kVerboseLevelFormatSpecifier, base::FormatFlags::VerboseLevel);
            // For date/time we need to extract user's date format first
            std::size_t dateIndex = std::string::npos;
            if ((dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier)) != std::string::npos) {
                while (dateIndex > 0 && formatCopy[dateIndex - 1] == base::consts::kFormatSpecifierChar) {
                    dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier, dateIndex + 1);
                }
                if (dateIndex != std::string::npos) {
                    addFlag(base::FormatFlags::DateTime);
                    updateDateFormat(dateIndex, formatCopy);
                }
            }
            m_format = formatCopy;
            updateFormatSpec();
        }

        void LogFormat::updateDateFormat(std::size_t index, base::type::string_t &currFormat) {
            if (hasFlag(base::FormatFlags::DateTime)) {
                index += ELPP_STRLEN(base::consts::kDateTimeFormatSpecifier);
            }
            const base::type::char_t *ptr = currFormat.c_str() + index;
            if ((currFormat.size() > index) && (ptr[0] == '{')) {
                // User has provided format for date/time
                ++ptr;
                int count = 1;  // Start by 1 in order to remove starting brace
                std::stringstream ss;
                for (; *ptr; ++ptr, ++count) {
                    if (*ptr == '}') {
                        ++count;  // In order to remove ending brace
                        break;
                    }
                    ss << static_cast<char>(*ptr);
                }
                currFormat.erase(index, count);
                m_dateTimeFormat = ss.str();
            } else {
                // No format provided, use default
                if (hasFlag(base::FormatFlags::DateTime)) {
                    m_dateTimeFormat = std::string(base::consts::kDefaultDateTimeFormat);
                }
            }
        }

        void LogFormat::updateFormatSpec(void) {
            // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet.
            if (m_level == Level::Debug) {
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
                                                         base::consts::kDebugLevelLogValue);
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
                                                         base::consts::kDebugLevelShortLogValue);
            } else if (m_level == Level::Info) {
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
                                                         base::consts::kInfoLevelLogValue);
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
                                                         base::consts::kInfoLevelShortLogValue);
            } else if (m_level == Level::Warning) {
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
                                                         base::consts::kWarningLevelLogValue);
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
                                                         base::consts::kWarningLevelShortLogValue);
            } else if (m_level == Level::Error) {
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
                                                         base::consts::kErrorLevelLogValue);
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
                                                         base::consts::kErrorLevelShortLogValue);
            } else if (m_level == Level::Fatal) {
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
                                                         base::consts::kFatalLevelLogValue);
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
                                                         base::consts::kFatalLevelShortLogValue);
            } else if (m_level == Level::Verbose) {
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
                                                         base::consts::kVerboseLevelLogValue);
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
                                                         base::consts::kVerboseLevelShortLogValue);
            } else if (m_level == Level::Trace) {
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
                                                         base::consts::kTraceLevelLogValue);
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
                                                         base::consts::kTraceLevelShortLogValue);
            }
            if (hasFlag(base::FormatFlags::User)) {
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentUserFormatSpecifier,
                                                         m_currentUser);
            }
            if (hasFlag(base::FormatFlags::Host)) {
                base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentHostFormatSpecifier,
                                                         m_currentHost);
            }
            // Ignore Level::Global and Level::Unknown
        }

// TypedConfigurations

        TypedConfigurations::TypedConfigurations(Configurations *configurations,
                                                 base::LogStreamsReferenceMap *logStreamsReference) {
            m_configurations = configurations;
            m_logStreamsReference = logStreamsReference;
            build(m_configurations);
        }

        TypedConfigurations::TypedConfigurations(const TypedConfigurations &other) {
            this->m_configurations = other.m_configurations;
            this->m_logStreamsReference = other.m_logStreamsReference;
            build(m_configurations);
        }

        bool TypedConfigurations::enabled(Level
        level) {
        return
        getConfigByVal<bool>(level, &m_enabledMap,
        "enabled");
    }

    bool TypedConfigurations::toFile(Level level) {
        return getConfigByVal<bool>(level, &m_toFileMap, "toFile");
    }

    const std::string &TypedConfigurations::filename(Level level) {
        return getConfigByRef<std::string>(level, &m_filenameMap, "filename");
    }

    bool TypedConfigurations::toStandardOutput(Level level) {
        return getConfigByVal<bool>(level, &m_toStandardOutputMap, "toStandardOutput");
    }

    const base::LogFormat &TypedConfigurations::logFormat(Level level) {
        return getConfigByRef<base::LogFormat>(level, &m_logFormatMap, "logFormat");
    }

    const base::SubsecondPrecision &TypedConfigurations::subsecondPrecision(Level level) {
        return getConfigByRef<base::SubsecondPrecision>(level, &m_subsecondPrecisionMap, "subsecondPrecision");
    }

    const base::MillisecondsWidth &TypedConfigurations::millisecondsWidth(Level level) {
        return getConfigByRef<base::MillisecondsWidth>(level, &m_subsecondPrecisionMap, "millisecondsWidth");
    }

    bool TypedConfigurations::performanceTracking(Level level) {
        return getConfigByVal<bool>(level, &m_performanceTrackingMap, "performanceTracking");
    }

    base::type::fstream_t *TypedConfigurations::fileStream(Level level) {
        return getConfigByRef<base::FileStreamPtr>(level, &m_fileStreamMap, "fileStream").get();
    }

    std::size_t TypedConfigurations::maxLogFileSize(Level level) {
        return getConfigByVal<std::size_t>(level, &m_maxLogFileSizeMap, "maxLogFileSize");
    }

    std::size_t TypedConfigurations::logFlushThreshold(Level level) {
        return getConfigByVal<std::size_t>(level, &m_logFlushThresholdMap, "logFlushThreshold");
    }

    void TypedConfigurations::build(Configurations *configurations) {
        base::threading::ScopedLock scopedLock(lock());
        auto getBool = [](std::string boolStr) -> bool {  // Pass by value for trimming
            base::utils::Str::trim(boolStr);
            return (boolStr == "TRUE" || boolStr == "true" || boolStr == "1");
        };
        std::vector<Configuration *> withFileSizeLimit;
        for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) {
            Configuration *conf = *it;
            // We cannot use switch on strong enums because Intel C++ dont support them yet
            if (conf->configurationType() == ConfigurationType::Enabled) {
                setValue(conf->level(), getBool(conf->value()), &m_enabledMap);
            } else if (conf->configurationType() == ConfigurationType::ToFile) {
                setValue(conf->level(), getBool(conf->value()), &m_toFileMap);
            } else if (conf->configurationType() == ConfigurationType::ToStandardOutput) {
                setValue(conf->level(), getBool(conf->value()), &m_toStandardOutputMap);
            } else if (conf->configurationType() == ConfigurationType::Filename) {
                // We do not yet configure filename but we will configure in another
                // loop. This is because if file cannot be created, we will force ToFile
                // to be false. Because configuring logger is not necessarily performance
                // sensative operation, we can live with another loop; (by the way this loop
                // is not very heavy either)
            } else if (conf->configurationType() == ConfigurationType::Format) {
                setValue(conf->level(), base::LogFormat(conf->level(),
                                                        base::type::string_t(conf->value().begin(),
                                                                             conf->value().end())), &m_logFormatMap);
            } else if (conf->configurationType() == ConfigurationType::SubsecondPrecision) {
                setValue(Level::Global,
                         base::SubsecondPrecision(static_cast<int>(getULong(conf->value()))), &m_subsecondPrecisionMap);
            } else if (conf->configurationType() == ConfigurationType::PerformanceTracking) {
                setValue(Level::Global, getBool(conf->value()), &m_performanceTrackingMap);
            } else if (conf->configurationType() == ConfigurationType::MaxLogFileSize) {
                setValue(conf->level(), static_cast<std::size_t>(getULong(conf->value())), &m_maxLogFileSizeMap);
#if !defined(ELPP_NO_DEFAULT_LOG_FILE)
                withFileSizeLimit.push_back(conf);
#endif  // !defined(ELPP_NO_DEFAULT_LOG_FILE)
            } else if (conf->configurationType() == ConfigurationType::LogFlushThreshold) {
                setValue(conf->level(), static_cast<std::size_t>(getULong(conf->value())), &m_logFlushThresholdMap);
            }
        }
        // As mentioned earlier, we will now set filename configuration in separate loop to deal with non-existent files
        for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) {
            Configuration *conf = *it;
            if (conf->configurationType() == ConfigurationType::Filename) {
                insertFile(conf->level(), conf->value());
            }
        }
        for (std::vector<Configuration *>::iterator conf = withFileSizeLimit.begin();
             conf != withFileSizeLimit.end(); ++conf) {
            // This is not unsafe as mutex is locked in currect scope
            unsafeValidateFileRolling((*conf)->level(), base::defaultPreRollOutCallback);
        }
    }

    unsigned long TypedConfigurations::getULong(std::string confVal) {
        bool valid = true;
        base::utils::Str::trim(confVal);
        valid = !confVal.empty() && std::find_if(confVal.begin(), confVal.end(),
                                                 [](char c) {
                                                     return !base::utils::Str::isDigit(c);
                                                 }) == confVal.end();
        if (!valid) {
            valid = false;
            ELPP_ASSERT(valid, "Configuration value not a valid integer [" << confVal << "]");
            return 0;
        }
        return atol(confVal.c_str());
    }

    std::string TypedConfigurations::resolveFilename(const std::string &filename) {
        std::string resultingFilename = filename;
        std::size_t dateIndex = std::string::npos;
        std::string dateTimeFormatSpecifierStr = std::string(base::consts::kDateTimeFormatSpecifierForFilename);
        if ((dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str())) != std::string::npos) {
            while (dateIndex > 0 && resultingFilename[dateIndex - 1] == base::consts::kFormatSpecifierChar) {
                dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str(), dateIndex + 1);
            }
            if (dateIndex != std::string::npos) {
                const char *ptr = resultingFilename.c_str() + dateIndex;
                // Goto end of specifier
                ptr += dateTimeFormatSpecifierStr.size();
                std::string fmt;
                if ((resultingFilename.size() > dateIndex) && (ptr[0] == '{')) {
                    // User has provided format for date/time
                    ++ptr;
                    int count = 1;  // Start by 1 in order to remove starting brace
                    std::stringstream ss;
                    for (; *ptr; ++ptr, ++count) {
                        if (*ptr == '}') {
                            ++count;  // In order to remove ending brace
                            break;
                        }
                        ss << *ptr;
                    }
                    resultingFilename.erase(dateIndex + dateTimeFormatSpecifierStr.size(), count);
                    fmt = ss.str();
                } else {
                    fmt = std::string(base::consts::kDefaultDateTimeFormatInFilename);
                }
                base::SubsecondPrecision ssPrec(3);
                std::string now = base::utils::DateTime::getDateTime(fmt.c_str(), &ssPrec);
                base::utils::Str::replaceAll(now, '/', '-'); // Replace path element since we are dealing with filename
                base::utils::Str::replaceAll(resultingFilename, dateTimeFormatSpecifierStr, now);
            }
        }
        return resultingFilename;
    }

    void TypedConfigurations::insertFile(Level level, const std::string &fullFilename) {
#if defined(ELPP_NO_LOG_TO_FILE)
        setValue(level, false, &m_toFileMap);
ELPP_UNUSED(fullFilename);
m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(nullptr)));
return;
#endif
        std::string resolvedFilename = resolveFilename(fullFilename);
        if (resolvedFilename.empty()) {
            std::cerr << "Could not load empty file for logging, please re-check your configurations for level ["
                      << LevelHelper::convertToString(level) << "]";
        }
        std::string filePath = base::utils::File::extractPathFromFilename(resolvedFilename,
                                                                          base::consts::kFilePathSeperator);
        if (filePath.size() < resolvedFilename.size()) {
            base::utils::File::createPath(filePath);
        }
        auto create = [&](Level level) {
            base::LogStreamsReferenceMap::iterator filestreamIter = m_logStreamsReference->find(resolvedFilename);
            base::type::fstream_t *fs = nullptr;
            if (filestreamIter == m_logStreamsReference->end()) {
                // We need a completely new stream, nothing to share with
                fs = base::utils::File::newFileStream(resolvedFilename);
                m_filenameMap.insert(std::make_pair(level, resolvedFilename));
                m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(fs)));
                m_logStreamsReference->insert(
                        std::make_pair(resolvedFilename, base::FileStreamPtr(m_fileStreamMap.at(level))));
            } else {
                // Woops! we have an existing one, share it!
                m_filenameMap.insert(std::make_pair(level, filestreamIter->first));
                m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(filestreamIter->second)));
                fs = filestreamIter->second.get();
            }
            if (fs == nullptr) {
                // We display bad file error from newFileStream()
                ELPP_INTERNAL_ERROR("Setting [TO_FILE] of ["
                                            << LevelHelper::convertToString(level) << "] to FALSE", false);
                setValue(level, false, &m_toFileMap);
            }
        };
        // If we dont have file conf for any level, create it for Level::Global first
        // otherwise create for specified level
        create(m_filenameMap.empty() && m_fileStreamMap.empty() ? Level::Global : level);
    }

    bool TypedConfigurations::unsafeValidateFileRolling(Level level, const PreRollOutCallback &preRollOutCallback) {
        base::type::fstream_t *fs = unsafeGetConfigByRef(level, &m_fileStreamMap, "fileStream").get();
        if (fs == nullptr) {
            return true;
        }
        std::size_t maxLogFileSize = unsafeGetConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize");
        std::size_t currFileSize = base::utils::File::getSizeOfFile(fs);
        if (maxLogFileSize != 0 && currFileSize >= maxLogFileSize) {
            std::string fname = unsafeGetConfigByRef(level, &m_filenameMap, "filename");
            ELPP_INTERNAL_INFO(1, "Truncating log file [" << fname << "] as a result of configurations for level ["
                                                          << LevelHelper::convertToString(level) << "]");
            fs->close();
            preRollOutCallback(fname.c_str(), currFileSize);
            fs->open(fname, std::fstream::out | std::fstream::trunc);
            return true;
        }
        return false;
    }

// RegisteredHitCounters

    bool RegisteredHitCounters::validateEveryN(const char *filename, base::type::LineNumber lineNumber, std::size_t n) {
        base::threading::ScopedLock scopedLock(lock());
        base::HitCounter *counter = get(filename, lineNumber);
        if (counter == nullptr) {
            registerNew(counter = new base::HitCounter(filename, lineNumber));
        }
        counter->validateHitCounts(n);
        bool result = (n >= 1 && counter->hitCounts() != 0 && counter->hitCounts() % n == 0);
        return result;
    }

/// @brief Validates counter for hits >= N, i.e, registers new if does not exist otherwise updates original one
/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned
    bool RegisteredHitCounters::validateAfterN(const char *filename, base::type::LineNumber lineNumber, std::size_t n) {
        base::threading::ScopedLock scopedLock(lock());
        base::HitCounter *counter = get(filename, lineNumber);
        if (counter == nullptr) {
            registerNew(counter = new base::HitCounter(filename, lineNumber));
        }
        // Do not use validateHitCounts here since we do not want to reset counter here
        // Note the >= instead of > because we are incrementing
        // after this check
        if (counter->hitCounts() >= n)
            return true;
        counter->increment();
        return false;
    }

/// @brief Validates counter for hits are <= n, i.e, registers new if does not exist otherwise updates original one
/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned
    bool RegisteredHitCounters::validateNTimes(const char *filename, base::type::LineNumber lineNumber, std::size_t n) {
        base::threading::ScopedLock scopedLock(lock());
        base::HitCounter *counter = get(filename, lineNumber);
        if (counter == nullptr) {
            registerNew(counter = new base::HitCounter(filename, lineNumber));
        }
        counter->increment();
        // Do not use validateHitCounts here since we do not want to reset counter here
        if (counter->hitCounts() <= n)
            return true;
        return false;
    }

// RegisteredLoggers

    RegisteredLoggers::RegisteredLoggers(const LogBuilderPtr &defaultLogBuilder) :
            m_defaultLogBuilder(defaultLogBuilder) {
        m_defaultConfigurations.setToDefault();
    }

    Logger *RegisteredLoggers::get(const std::string &id, bool forceCreation) {
        base::threading::ScopedLock scopedLock(lock());
        Logger *logger_ = base::utils::Registry<Logger, std::string>::get(id);
        if (logger_ == nullptr && forceCreation) {
            bool validId = Logger::isValidId(id);
            if (!validId) {
                ELPP_ASSERT(validId, "Invalid logger ID [" << id << "]. Not registering this logger.");
                return nullptr;
            }
            logger_ = new Logger(id, m_defaultConfigurations, &m_logStreamsReference);
            logger_->m_logBuilder = m_defaultLogBuilder;
            registerNew(id, logger_);
            LoggerRegistrationCallback *callback = nullptr;
            for (const std::pair<std::string, base::type::LoggerRegistrationCallbackPtr> &h
                    : m_loggerRegistrationCallbacks) {
                callback = h.second.get();
                if (callback != nullptr && callback->enabled()) {
                    callback->handle(logger_);
                }
            }
        }
        return logger_;
    }

    bool RegisteredLoggers::remove(const std::string &id) {
        if (id == base::consts::kDefaultLoggerId) {
            return false;
        }
        Logger *logger = base::utils::Registry<Logger, std::string>::get(id);
        if (logger != nullptr) {
            unregister(logger);
        }
        return true;
    }

    void RegisteredLoggers::unsafeFlushAll(void) {
        ELPP_INTERNAL_INFO(1, "Flushing all log files");
        for (base::LogStreamsReferenceMap::iterator it = m_logStreamsReference.begin();
             it != m_logStreamsReference.end(); ++it) {
            if (it->second.get() == nullptr) continue;
            it->second->flush();
        }
    }

// VRegistry

    VRegistry::VRegistry(base::type::VerboseLevel level, base::type::EnumType *pFlags) : m_level(level),
                                                                                         m_pFlags(pFlags) {
    }

/// @brief Sets verbose level. Accepted range is 0-9
    void VRegistry::setLevel(base::type::VerboseLevel level) {
        base::threading::ScopedLock scopedLock(lock());
        if (level > 9)
            m_level = base::consts::kMaxVerboseLevel;
        else
            m_level = level;
    }

    void VRegistry::setModules(const char *modules) {
        base::threading::ScopedLock scopedLock(lock());
        auto addSuffix = [](std::stringstream &ss, const char *sfx, const char *prev) {
            if (prev != nullptr && base::utils::Str::endsWith(ss.str(), std::string(prev))) {
                std::string chr(ss.str().substr(0, ss.str().size() - strlen(prev)));
                ss.str(std::string(""));
                ss << chr;
            }
            if (base::utils::Str::endsWith(ss.str(), std::string(sfx))) {
                std::string chr(ss.str().substr(0, ss.str().size() - strlen(sfx)));
                ss.str(std::string(""));
                ss << chr;
            }
            ss << sfx;
        };
        auto insert = [&](std::stringstream &ss, base::type::VerboseLevel level) {
            if (!base::utils::hasFlag(LoggingFlag::DisableVModulesExtensions, *m_pFlags)) {
                addSuffix(ss, ".h", nullptr);
                m_modules.insert(std::make_pair(ss.str(), level));
                addSuffix(ss, ".c", ".h");
                m_modules.insert(std::make_pair(ss.str(), level));
                addSuffix(ss, ".cpp", ".c");
                m_modules.insert(std::make_pair(ss.str(), level));
                addSuffix(ss, ".cc", ".cpp");
                m_modules.insert(std::make_pair(ss.str(), level));
                addSuffix(ss, ".cxx", ".cc");
                m_modules.insert(std::make_pair(ss.str(), level));
                addSuffix(ss, ".-inl.h", ".cxx");
                m_modules.insert(std::make_pair(ss.str(), level));
                addSuffix(ss, ".hxx", ".-inl.h");
                m_modules.insert(std::make_pair(ss.str(), level));
                addSuffix(ss, ".hpp", ".hxx");
                m_modules.insert(std::make_pair(ss.str(), level));
                addSuffix(ss, ".hh", ".hpp");
            }
            m_modules.insert(std::make_pair(ss.str(), level));
        };
        bool isMod = true;
        bool isLevel = false;
        std::stringstream ss;
        int level = -1;
        for (; *modules; ++modules) {
            switch (*modules) {
                case '=':
                    isLevel = true;
                    isMod = false;
                    break;
                case ',':
                    isLevel = false;
                    isMod = true;
                    if (!ss.str().empty() && level != -1) {
                        insert(ss, static_cast<base::type::VerboseLevel>(level));
                        ss.str(std::string(""));
                        level = -1;
                    }
                    break;
                default:
                    if (isMod) {
                        ss << *modules;
                    } else if (isLevel) {
                        if (isdigit(*modules)) {
                            level = static_cast<base::type::VerboseLevel>(*modules) - 48;
                        }
                    }
                    break;
            }
        }
        if (!ss.str().empty() && level != -1) {
            insert(ss, static_cast<base::type::VerboseLevel>(level));
        }
    }

    bool VRegistry::allowed(base::type::VerboseLevel vlevel, const char *file) {
        base::threading::ScopedLock scopedLock(lock());
        if (m_modules.empty() || file == nullptr) {
            return vlevel <= m_level;
        } else {
            char baseFilename[base::consts::kSourceFilenameMaxLength] = "";
            base::utils::File::buildBaseFilename(file, baseFilename);
            std::map<std::string, base::type::VerboseLevel>::iterator it = m_modules.begin();
            for (; it != m_modules.end(); ++it) {
                if (base::utils::Str::wildCardMatch(baseFilename, it->first.c_str())) {
                    return vlevel <= it->second;
                }
            }
            if (base::utils::hasFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified, *m_pFlags)) {
                return true;
            }
            return false;
        }
    }

    void VRegistry::setFromArgs(const base::utils::CommandLineArgs *commandLineArgs) {
        if (commandLineArgs->hasParam("-v") || commandLineArgs->hasParam("--verbose") ||
            commandLineArgs->hasParam("-V") || commandLineArgs->hasParam("--VERBOSE")) {
            setLevel(base::consts::kMaxVerboseLevel);
        } else if (commandLineArgs->hasParamWithValue("--v")) {
            setLevel(static_cast<base::type::VerboseLevel>(atoi(commandLineArgs->getParamValue("--v"))));
        } else if (commandLineArgs->hasParamWithValue("--V")) {
            setLevel(static_cast<base::type::VerboseLevel>(atoi(commandLineArgs->getParamValue("--V"))));
        } else if ((commandLineArgs->hasParamWithValue("-vmodule")) && vModulesEnabled()) {
            setModules(commandLineArgs->getParamValue("-vmodule"));
        } else if (commandLineArgs->hasParamWithValue("-VMODULE") && vModulesEnabled()) {
            setModules(commandLineArgs->getParamValue("-VMODULE"));
        }
    }

#if !defined(ELPP_DEFAULT_LOGGING_FLAGS)
#   define ELPP_DEFAULT_LOGGING_FLAGS 0x0
#endif // !defined(ELPP_DEFAULT_LOGGING_FLAGS)
// Storage
#if ELPP_ASYNC_LOGGING
    Storage::Storage(const LogBuilderPtr& defaultLogBuilder, base::IWorker* asyncDispatchWorker) :
#else

    Storage::Storage(const LogBuilderPtr &defaultLogBuilder) :
#endif  // ELPP_ASYNC_LOGGING
            m_registeredHitCounters(new base::RegisteredHitCounters()),
            m_registeredLoggers(new base::RegisteredLoggers(defaultLogBuilder)),
            m_flags(ELPP_DEFAULT_LOGGING_FLAGS),
            m_vRegistry(new base::VRegistry(0, &m_flags)),
#if ELPP_ASYNC_LOGGING
    m_asyncLogQueue(new base::AsyncLogQueue()),
m_asyncDispatchWorker(asyncDispatchWorker),
#endif  // ELPP_ASYNC_LOGGING
            m_preRollOutCallback(base::defaultPreRollOutCallback) {
        // Register default logger
        m_registeredLoggers->get(std::string(base::consts::kDefaultLoggerId));
        // We register default logger anyway (worse case it's not going to register) just in case
        m_registeredLoggers->get("default");
        // Register performance logger and reconfigure format
        Logger *performanceLogger = m_registeredLoggers->get(std::string(base::consts::kPerformanceLoggerId));
        m_registeredLoggers->get("performance");
        performanceLogger->configurations()->setGlobally(ConfigurationType::Format,
                                                         std::string("%datetime %level %msg"));
        performanceLogger->reconfigure();
#if defined(ELPP_SYSLOG)
        // Register syslog logger and reconfigure format
Logger* sysLogLogger = m_registeredLoggers->get(std::string(base::consts::kSysLogLoggerId));
sysLogLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%level: %msg"));
sysLogLogger->reconfigure();
#endif //  defined(ELPP_SYSLOG)
        addFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified);
#if ELPP_ASYNC_LOGGING
        installLogDispatchCallback<base::AsyncLogDispatchCallback>(std::string("AsyncLogDispatchCallback"));
#else
        installLogDispatchCallback<base::DefaultLogDispatchCallback>(std::string("DefaultLogDispatchCallback"));
#endif  // ELPP_ASYNC_LOGGING
#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
        installPerformanceTrackingCallback<base::DefaultPerformanceTrackingCallback>
(std::string("DefaultPerformanceTrackingCallback"));
#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
        ELPP_INTERNAL_INFO(1, "Easylogging++ has been initialized");
#if ELPP_ASYNC_LOGGING
        m_asyncDispatchWorker->start();
#endif  // ELPP_ASYNC_LOGGING
    }

    Storage::~Storage(void) {
        ELPP_INTERNAL_INFO(4, "Destroying storage");
#if ELPP_ASYNC_LOGGING
        ELPP_INTERNAL_INFO(5, "Replacing log dispatch callback to synchronous");
uninstallLogDispatchCallback<base::AsyncLogDispatchCallback>(std::string("AsyncLogDispatchCallback"));
installLogDispatchCallback<base::DefaultLogDispatchCallback>(std::string("DefaultLogDispatchCallback"));
ELPP_INTERNAL_INFO(5, "Destroying asyncDispatchWorker");
base::utils::safeDelete(m_asyncDispatchWorker);
ELPP_INTERNAL_INFO(5, "Destroying asyncLogQueue");
base::utils::safeDelete(m_asyncLogQueue);
#endif  // ELPP_ASYNC_LOGGING
        ELPP_INTERNAL_INFO(5, "Destroying registeredHitCounters");
        base::utils::safeDelete(m_registeredHitCounters);
        ELPP_INTERNAL_INFO(5, "Destroying registeredLoggers");
        base::utils::safeDelete(m_registeredLoggers);
        ELPP_INTERNAL_INFO(5, "Destroying vRegistry");
        base::utils::safeDelete(m_vRegistry);
    }

    bool Storage::hasCustomFormatSpecifier(const char *formatSpecifier) {
        base::threading::ScopedLock scopedLock(lock());
        return std::find(m_customFormatSpecifiers.begin(), m_customFormatSpecifiers.end(),
                         formatSpecifier) != m_customFormatSpecifiers.end();
    }

    void Storage::installCustomFormatSpecifier(const CustomFormatSpecifier &customFormatSpecifier) {
        if (hasCustomFormatSpecifier(customFormatSpecifier.formatSpecifier())) {
            return;
        }
        base::threading::ScopedLock scopedLock(lock());
        m_customFormatSpecifiers.push_back(customFormatSpecifier);
    }

    bool Storage::uninstallCustomFormatSpecifier(const char *formatSpecifier) {
        base::threading::ScopedLock scopedLock(lock());
        std::vector<CustomFormatSpecifier>::iterator it = std::find(m_customFormatSpecifiers.begin(),
                                                                    m_customFormatSpecifiers.end(), formatSpecifier);
        if (it != m_customFormatSpecifiers.end() && strcmp(formatSpecifier, it->formatSpecifier()) == 0) {
            m_customFormatSpecifiers.erase(it);
            return true;
        }
        return false;
    }

    void Storage::setApplicationArguments(int argc, char **argv) {
        m_commandLineArgs.setArgs(argc, argv);
        m_vRegistry->setFromArgs(commandLineArgs());
        // default log file
#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
        if (m_commandLineArgs.hasParamWithValue(base::consts::kDefaultLogFileParam)) {
            Configurations c;
            c.setGlobally(ConfigurationType::Filename,
                          std::string(m_commandLineArgs.getParamValue(base::consts::kDefaultLogFileParam)));
            registeredLoggers()->setDefaultConfigurations(c);
            for (base::RegisteredLoggers::iterator it = registeredLoggers()->begin();
                 it != registeredLoggers()->end(); ++it) {
                it->second->configure(c);
            }
        }
#endif  // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
#if defined(ELPP_LOGGING_FLAGS_FROM_ARG)
        if (m_commandLineArgs.hasParamWithValue(base::consts::kLoggingFlagsParam)) {
int userInput = atoi(m_commandLineArgs.getParamValue(base::consts::kLoggingFlagsParam));
if (ELPP_DEFAULT_LOGGING_FLAGS == 0x0) {
  m_flags = userInput;
} else {
  base::utils::addFlag<base::type::EnumType>(userInput, &m_flags);
}
}
#endif  // defined(ELPP_LOGGING_FLAGS_FROM_ARG)
    }

// DefaultLogDispatchCallback

    void DefaultLogDispatchCallback::handle(const LogDispatchData *data) {
        m_data = data;
        dispatch(m_data->logMessage()->logger()->logBuilder()->build(m_data->logMessage(),
                                                                     m_data->dispatchAction() ==
                                                                     base::DispatchAction::NormalLog));
    }

    void DefaultLogDispatchCallback::dispatch(base::type::string_t &&logLine) {
        if (m_data->dispatchAction() == base::DispatchAction::NormalLog) {
            if (m_data->logMessage()->logger()->m_typedConfigurations->toFile(m_data->logMessage()->level())) {
                base::type::fstream_t *fs = m_data->logMessage()->logger()->m_typedConfigurations->fileStream(
                        m_data->logMessage()->level());
                if (fs != nullptr) {
                    fs->write(logLine.c_str(), logLine.size());
                    if (fs->fail()) {
                        ELPP_INTERNAL_ERROR("Unable to write log to file ["
                                                    << m_data->logMessage()->logger()->m_typedConfigurations->filename(
                                                            m_data->logMessage()->level()) << "].\n"
                                                    << "Few possible reasons (could be something else):\n"
                                                    << "      * Permission denied\n"
                                                    << "      * Disk full\n" << "      * Disk is not writable", true);
                    } else {
                        if (ELPP->hasFlag(LoggingFlag::ImmediateFlush)
                            || (m_data->logMessage()->logger()->isFlushNeeded(m_data->logMessage()->level()))) {
                            m_data->logMessage()->logger()->flush(m_data->logMessage()->level(), fs);
                        }
                    }
                } else {
                    ELPP_INTERNAL_ERROR(
                            "Log file for [" << LevelHelper::convertToString(m_data->logMessage()->level()) << "] "
                                             << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: "
                                             << m_data->logMessage()->logger()->id() << "]", false);
                }
            }
            if (m_data->logMessage()->logger()->m_typedConfigurations->toStandardOutput(
                    m_data->logMessage()->level())) {
                if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput))
                    m_data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine,
                                                                                         m_data->logMessage()->level());
                ELPP_COUT << ELPP_COUT_LINE(logLine);
            }
        }
#if defined(ELPP_SYSLOG)
        else if (m_data->dispatchAction() == base::DispatchAction::SysLog) {
// Determine syslog priority
int sysLogPriority = 0;
if (m_data->logMessage()->level() == Level::Fatal)
  sysLogPriority = LOG_EMERG;
else if (m_data->logMessage()->level() == Level::Error)
  sysLogPriority = LOG_ERR;
else if (m_data->logMessage()->level() == Level::Warning)
  sysLogPriority = LOG_WARNING;
else if (m_data->logMessage()->level() == Level::Info)
  sysLogPriority = LOG_INFO;
else if (m_data->logMessage()->level() == Level::Debug)
  sysLogPriority = LOG_DEBUG;
else
  sysLogPriority = LOG_NOTICE;
#  if defined(ELPP_UNICODE)
char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str());
syslog(sysLogPriority, "%s", line);
free(line);
#  else
syslog(sysLogPriority, "%s", logLine.c_str());
#  endif
}
#endif  // defined(ELPP_SYSLOG)
    }

#if ELPP_ASYNC_LOGGING

    // AsyncLogDispatchCallback

void AsyncLogDispatchCallback::handle(const LogDispatchData* data) {
base::type::string_t logLine = data->logMessage()->logger()->logBuilder()->build(data->logMessage(),
                             data->dispatchAction() == base::DispatchAction::NormalLog);
if (data->dispatchAction() == base::DispatchAction::NormalLog
  && data->logMessage()->logger()->typedConfigurations()->toStandardOutput(data->logMessage()->level())) {
if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput))
  data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, data->logMessage()->level());
ELPP_COUT << ELPP_COUT_LINE(logLine);
}
// Save resources and only queue if we want to write to file otherwise just ignore handler
if (data->logMessage()->logger()->typedConfigurations()->toFile(data->logMessage()->level())) {
ELPP->asyncLogQueue()->push(AsyncLogItem(*(data->logMessage()), *data, logLine));
}
}

// AsyncDispatchWorker
AsyncDispatchWorker::AsyncDispatchWorker() {
setContinueRunning(false);
}

AsyncDispatchWorker::~AsyncDispatchWorker() {
setContinueRunning(false);
ELPP_INTERNAL_INFO(6, "Stopping dispatch worker - Cleaning log queue");
clean();
ELPP_INTERNAL_INFO(6, "Log queue cleaned");
}

bool AsyncDispatchWorker::clean(void) {
std::mutex m;
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return !ELPP->asyncLogQueue()->empty(); });
emptyQueue();
lk.unlock();
cv.notify_one();
return ELPP->asyncLogQueue()->empty();
}

void AsyncDispatchWorker::emptyQueue(void) {
while (!ELPP->asyncLogQueue()->empty()) {
AsyncLogItem data = ELPP->asyncLogQueue()->next();
handle(&data);
base::threading::msleep(100);
}
}

void AsyncDispatchWorker::start(void) {
base::threading::msleep(5000); // 5s (why?)
setContinueRunning(true);
std::thread t1(&AsyncDispatchWorker::run, this);
t1.join();
}

void AsyncDispatchWorker::handle(AsyncLogItem* logItem) {
LogDispatchData* data = logItem->data();
LogMessage* logMessage = logItem->logMessage();
Logger* logger = logMessage->logger();
base::TypedConfigurations* conf = logger->typedConfigurations();
base::type::string_t logLine = logItem->logLine();
if (data->dispatchAction() == base::DispatchAction::NormalLog) {
if (conf->toFile(logMessage->level())) {
  base::type::fstream_t* fs = conf->fileStream(logMessage->level());
  if (fs != nullptr) {
    fs->write(logLine.c_str(), logLine.size());
    if (fs->fail()) {
      ELPP_INTERNAL_ERROR("Unable to write log to file ["
                          << conf->filename(logMessage->level()) << "].\n"
                          << "Few possible reasons (could be something else):\n" << "      * Permission denied\n"
                          << "      * Disk full\n" << "      * Disk is not writable", true);
    } else {
      if (ELPP->hasFlag(LoggingFlag::ImmediateFlush) || (logger->isFlushNeeded(logMessage->level()))) {
        logger->flush(logMessage->level(), fs);
      }
    }
  } else {
    ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(logMessage->level()) << "] "
                        << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " << logger->id() << "]", false);
  }
}
}
#  if defined(ELPP_SYSLOG)
else if (data->dispatchAction() == base::DispatchAction::SysLog) {
// Determine syslog priority
int sysLogPriority = 0;
if (logMessage->level() == Level::Fatal)
  sysLogPriority = LOG_EMERG;
else if (logMessage->level() == Level::Error)
  sysLogPriority = LOG_ERR;
else if (logMessage->level() == Level::Warning)
  sysLogPriority = LOG_WARNING;
else if (logMessage->level() == Level::Info)
  sysLogPriority = LOG_INFO;
else if (logMessage->level() == Level::Debug)
  sysLogPriority = LOG_DEBUG;
else
  sysLogPriority = LOG_NOTICE;
#      if defined(ELPP_UNICODE)
char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str());
syslog(sysLogPriority, "%s", line);
free(line);
#      else
syslog(sysLogPriority, "%s", logLine.c_str());
#      endif
}
#  endif  // defined(ELPP_SYSLOG)
}

void AsyncDispatchWorker::run(void) {
while (continueRunning()) {
emptyQueue();
base::threading::msleep(10); // 10ms
}
}
#endif  // ELPP_ASYNC_LOGGING

// DefaultLogBuilder

    base::type::string_t DefaultLogBuilder::build(const LogMessage *logMessage, bool appendNewLine) const {
        base::TypedConfigurations *tc = logMessage->logger()->typedConfigurations();
        const base::LogFormat *logFormat = &tc->logFormat(logMessage->level());
        base::type::string_t logLine = logFormat->format();
        char buff[base::consts::kSourceFilenameMaxLength + base::consts::kSourceLineMaxLength] = "";
        const char *bufLim = buff + sizeof(buff);
        if (logFormat->hasFlag(base::FormatFlags::AppName)) {
            // App name
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kAppNameFormatSpecifier,
                                                     logMessage->logger()->parentApplicationName());
        }
        if (logFormat->hasFlag(base::FormatFlags::ThreadId)) {
            // Thread ID
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kThreadIdFormatSpecifier,
                                                     ELPP->getThreadName(base::threading::getCurrentThreadId()));
        }
        if (logFormat->hasFlag(base::FormatFlags::DateTime)) {
            // DateTime
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kDateTimeFormatSpecifier,
                                                     base::utils::DateTime::getDateTime(
                                                             logFormat->dateTimeFormat().c_str(),
                                                             &tc->subsecondPrecision(logMessage->level())));
        }
        if (logFormat->hasFlag(base::FormatFlags::Function)) {
            // Function
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFunctionFormatSpecifier,
                                                     logMessage->func());
        }
        if (logFormat->hasFlag(base::FormatFlags::File)) {
            // File
            base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength);
            base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff);
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileFormatSpecifier, std::string(buff));
        }
        if (logFormat->hasFlag(base::FormatFlags::FileBase)) {
            // FileBase
            base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength);
            base::utils::File::buildBaseFilename(logMessage->file(), buff);
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileBaseFormatSpecifier,
                                                     std::string(buff));
        }
        if (logFormat->hasFlag(base::FormatFlags::Line)) {
            // Line
            char *buf = base::utils::Str::clearBuff(buff, base::consts::kSourceLineMaxLength);
            buf = base::utils::Str::convertAndAddToBuff(logMessage->line(), base::consts::kSourceLineMaxLength, buf,
                                                        bufLim, false);
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLineFormatSpecifier, std::string(buff));
        }
        if (logFormat->hasFlag(base::FormatFlags::Location)) {
            // Location
            char *buf = base::utils::Str::clearBuff(buff,
                                                    base::consts::kSourceFilenameMaxLength +
                                                    base::consts::kSourceLineMaxLength);
            base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff);
            buf = base::utils::Str::addToBuff(buff, buf, bufLim);
            buf = base::utils::Str::addToBuff(":", buf, bufLim);
            buf = base::utils::Str::convertAndAddToBuff(logMessage->line(), base::consts::kSourceLineMaxLength, buf,
                                                        bufLim,
                                                        false);
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLocationFormatSpecifier,
                                                     std::string(buff));
        }
        if (logMessage->level() == Level::Verbose && logFormat->hasFlag(base::FormatFlags::VerboseLevel)) {
            // Verbose level
            char *buf = base::utils::Str::clearBuff(buff, 1);
            buf = base::utils::Str::convertAndAddToBuff(logMessage->verboseLevel(), 1, buf, bufLim, false);
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kVerboseLevelFormatSpecifier,
                                                     std::string(buff));
        }
        if (logFormat->hasFlag(base::FormatFlags::LogMessage)) {
            // Log message
            base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kMessageFormatSpecifier,
                                                     logMessage->message());
        }
#if !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS)
        for (std::vector<CustomFormatSpecifier>::const_iterator it = ELPP->customFormatSpecifiers()->begin();
             it != ELPP->customFormatSpecifiers()->end(); ++it) {
            std::string fs(it->formatSpecifier());
            base::type::string_t wcsFormatSpecifier(fs.begin(), fs.end());
            base::utils::Str::replaceFirstWithEscape(logLine, wcsFormatSpecifier, it->resolver()(logMessage));
        }
#endif  // !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS)
        if (appendNewLine) logLine += ELPP_LITERAL("\n");
        return logLine;
    }

// LogDispatcher

    void LogDispatcher::dispatch(void) {
        if (m_proceed && m_dispatchAction == base::DispatchAction::None) {
            m_proceed = false;
        }
        if (!m_proceed) {
            return;
        }
        base::threading::ScopedLock scopedLock(ELPP->lock());
        base::TypedConfigurations *tc = m_logMessage.logger()->m_typedConfigurations;
        if (ELPP->hasFlag(LoggingFlag::StrictLogFileSizeCheck)) {
            tc->validateFileRolling(m_logMessage.level(), ELPP->preRollOutCallback());
        }
        LogDispatchCallback *callback = nullptr;
        LogDispatchData data;
        for (const std::pair<std::string, base::type::LogDispatchCallbackPtr> &h
                : ELPP->m_logDispatchCallbacks) {
            callback = h.second.get();
            if (callback != nullptr && callback->enabled()) {
                data.setLogMessage(&m_logMessage);
                data.setDispatchAction(m_dispatchAction);
                callback->handle(&data);
            }
        }
    }

// MessageBuilder

    void MessageBuilder::initialize(Logger *logger) {
        m_logger = logger;
        m_containerLogSeperator = ELPP->hasFlag(LoggingFlag::NewLineForContainer) ?
                                  ELPP_LITERAL("\n    ") : ELPP_LITERAL(", ");
    }

    MessageBuilder &MessageBuilder::operator<<(const wchar_t *msg) {
        if (msg == nullptr) {
            m_logger->stream() << base::consts::kNullPointer;
            return *this;
        }
#  if defined(ELPP_UNICODE)
        m_logger->stream() << msg;
#  else
        char *buff_ = base::utils::Str::wcharPtrToCharPtr(msg);
        m_logger->stream() << buff_;
        free(buff_);
#  endif
        if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) {
            m_logger->stream() << " ";
        }
        return *this;
    }

// Writer

    Writer &Writer::construct(Logger *logger, bool needLock) {
        m_logger = logger;
        initializeLogger(logger->id(), false, needLock);
        m_messageBuilder.initialize(m_logger);
        return *this;
    }

    Writer &Writer::construct(int count, const char *loggerIds, ...) {
        if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) {
            va_list loggersList;
            va_start(loggersList, loggerIds);
            const char *id = loggerIds;
            for (int i = 0; i < count; ++i) {
                m_loggerIds.push_back(std::string(id));
                id = va_arg(loggersList, const char*);
            }
            va_end(loggersList);
            initializeLogger(m_loggerIds.at(0));
        } else {
            initializeLogger(std::string(loggerIds));
        }
        m_messageBuilder.initialize(m_logger);
        return *this;
    }

    void Writer::initializeLogger(const std::string &loggerId, bool lookup, bool needLock) {
        if (lookup) {
            m_logger = ELPP->registeredLoggers()->get(loggerId, ELPP->hasFlag(LoggingFlag::CreateLoggerAutomatically));
        }
        if (m_logger == nullptr) {
            ELPP->acquireLock();
            if (!ELPP->registeredLoggers()->has(std::string(base::consts::kDefaultLoggerId))) {
                // Somehow default logger has been unregistered. Not good! Register again
                ELPP->registeredLoggers()->get(std::string(base::consts::kDefaultLoggerId));
            }
            ELPP->releaseLock();  // Need to unlock it for next writer
            Writer(Level::Debug, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId)
                    << "Logger [" << loggerId << "] is not registered yet!";
            m_proceed = false;
        } else {
            if (needLock) {
                m_logger->acquireLock();  // This should not be unlocked by checking m_proceed because
                // m_proceed can be changed by lines below
            }
            if (ELPP->hasFlag(LoggingFlag::HierarchicalLogging)) {
                m_proceed = m_level == Level::Verbose ? m_logger->enabled(m_level) :
                            LevelHelper::castToInt(m_level) >= LevelHelper::castToInt(ELPP->m_loggingLevel);
            } else {
                m_proceed = m_logger->enabled(m_level);
            }
        }
    }

    void Writer::processDispatch() {
#if ELPP_LOGGING_ENABLED
        if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) {
            bool firstDispatched = false;
            base::type::string_t logMessage;
            std::size_t i = 0;
            do {
                if (m_proceed) {
                    if (firstDispatched) {
                        m_logger->stream() << logMessage;
                    } else {
                        firstDispatched = true;
                        if (m_loggerIds.size() > 1) {
                            logMessage = m_logger->stream().str();
                        }
                    }
                    triggerDispatch();
                } else if (m_logger != nullptr) {
                    m_logger->stream().str(ELPP_LITERAL(""));
                    m_logger->releaseLock();
                }
                if (i + 1 < m_loggerIds.size()) {
                    initializeLogger(m_loggerIds.at(i + 1));
                }
            } while (++i < m_loggerIds.size());
        } else {
            if (m_proceed) {
                triggerDispatch();
            } else if (m_logger != nullptr) {
                m_logger->stream().str(ELPP_LITERAL(""));
                m_logger->releaseLock();
            }
        }
#else
        if (m_logger != nullptr) {
            m_logger->stream().str(ELPP_LITERAL(""));
            m_logger->releaseLock();
        }
#endif // ELPP_LOGGING_ENABLED
    }

    void Writer::triggerDispatch(void) {
        if (m_proceed) {
            base::LogDispatcher(m_proceed, LogMessage(m_level, m_file, m_line, m_func, m_verboseLevel,
                                                      m_logger), m_dispatchAction).dispatch();
        }
        if (m_logger != nullptr) {
            m_logger->stream().str(ELPP_LITERAL(""));
            m_logger->releaseLock();
        }
        if (m_proceed && m_level == Level::Fatal
            && !ELPP->hasFlag(LoggingFlag::DisableApplicationAbortOnFatalLog)) {
            base::Writer(Level::Warning, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId)
                    << "Aborting application. Reason: Fatal log at [" << m_file << ":" << m_line << "]";
            std::stringstream reasonStream;
            reasonStream << "Fatal log at [" << m_file << ":" << m_line << "]"
                         << " If you wish to disable 'abort on fatal log' please use "
                         << "el::Helpers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog)";
            base::utils::abort(1, reasonStream.str());
        }
        m_proceed = false;
    }

// PErrorWriter

    PErrorWriter::~PErrorWriter(void) {
        if (m_proceed) {
#if ELPP_COMPILER_MSVC
            char buff[256];
strerror_s(buff, 256, errno);
m_logger->stream() << ": " << buff << " [" << errno << "]";
#else
            m_logger->stream() << ": " << strerror(errno) << " [" << errno << "]";
#endif
        }
    }

// PerformanceTracker

#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)

    PerformanceTracker::PerformanceTracker(const std::string& blockName,
                                   base::TimestampUnit timestampUnit,
                                   const std::string& loggerId,
                                   bool scopedLog, Level level) :
m_blockName(blockName), m_timestampUnit(timestampUnit), m_loggerId(loggerId), m_scopedLog(scopedLog),
m_level(level), m_hasChecked(false), m_lastCheckpointId(std::string()), m_enabled(false) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
// We store it locally so that if user happen to change configuration by the end of scope
// or before calling checkpoint, we still depend on state of configuraton at time of construction
el::Logger* loggerPtr = ELPP->registeredLoggers()->get(loggerId, false);
m_enabled = loggerPtr != nullptr && loggerPtr->m_typedConfigurations->performanceTracking(m_level);
if (m_enabled) {
base::utils::DateTime::gettimeofday(&m_startTime);
}
#endif  // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
}

PerformanceTracker::~PerformanceTracker(void) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
if (m_enabled) {
base::threading::ScopedLock scopedLock(lock());
if (m_scopedLog) {
  base::utils::DateTime::gettimeofday(&m_endTime);
  base::type::string_t formattedTime = getFormattedTimeTaken();
  PerformanceTrackingData data(PerformanceTrackingData::DataType::Complete);
  data.init(this);
  data.m_formattedTimeTaken = formattedTime;
  PerformanceTrackingCallback* callback = nullptr;
  for (const std::pair<std::string, base::type::PerformanceTrackingCallbackPtr>& h
       : ELPP->m_performanceTrackingCallbacks) {
    callback = h.second.get();
    if (callback != nullptr && callback->enabled()) {
      callback->handle(&data);
    }
  }
}
}
#endif  // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING)
}

void PerformanceTracker::checkpoint(const std::string& id, const char* file, base::type::LineNumber line,
                                const char* func) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
if (m_enabled) {
base::threading::ScopedLock scopedLock(lock());
base::utils::DateTime::gettimeofday(&m_endTime);
base::type::string_t formattedTime = m_hasChecked ? getFormattedTimeTaken(m_lastCheckpointTime) : ELPP_LITERAL("");
PerformanceTrackingData data(PerformanceTrackingData::DataType::Checkpoint);
data.init(this);
data.m_checkpointId = id;
data.m_file = file;
data.m_line = line;
data.m_func = func;
data.m_formattedTimeTaken = formattedTime;
PerformanceTrackingCallback* callback = nullptr;
for (const std::pair<std::string, base::type::PerformanceTrackingCallbackPtr>& h
     : ELPP->m_performanceTrackingCallbacks) {
  callback = h.second.get();
  if (callback != nullptr && callback->enabled()) {
    callback->handle(&data);
  }
}
base::utils::DateTime::gettimeofday(&m_lastCheckpointTime);
m_hasChecked = true;
m_lastCheckpointId = id;
}
#endif  // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
ELPP_UNUSED(id);
ELPP_UNUSED(file);
ELPP_UNUSED(line);
ELPP_UNUSED(func);
}

const base::type::string_t PerformanceTracker::getFormattedTimeTaken(struct timeval startTime) const {
if (ELPP->hasFlag(LoggingFlag::FixedTimeFormat)) {
base::type::stringstream_t ss;
ss << base::utils::DateTime::getTimeDifference(m_endTime,
    startTime, m_timestampUnit) << " " << base::consts::kTimeFormats[static_cast<base::type::EnumType>
        (m_timestampUnit)].unit;
return ss.str();
}
return base::utils::DateTime::formatTime(base::utils::DateTime::getTimeDifference(m_endTime,
     startTime, m_timestampUnit), m_timestampUnit);
}

#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)

    namespace debug {
#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG)

        // StackTrace

StackTrace::StackTraceEntry::StackTraceEntry(std::size_t index, const char* loc, const char* demang, const char* hex,
const char* addr) {
m_index = index;
m_location = std::string(loc);
m_demangled = std::string(demang);
m_hex = std::string(hex);
m_addr = std::string(addr);
}

std::ostream& operator<<(std::ostream& ss, const StackTrace::StackTraceEntry& si) {
ss << "[" << si.m_index << "] " << si.m_location << (si.m_demangled.empty() ? "" : ":") << si.m_demangled
 << (si.m_hex.empty() ? "" : "+") << si.m_hex << si.m_addr;
return ss;
}

std::ostream& operator<<(std::ostream& os, const StackTrace& st) {
std::vector<StackTrace::StackTraceEntry>::const_iterator it = st.m_stack.begin();
while (it != st.m_stack.end()) {
os << "    " << *it++ << "\n";
}
return os;
}

void StackTrace::generateNew(void) {
#if ELPP_STACKTRACE
m_stack.clear();
void* stack[kMaxStack];
unsigned int size = backtrace(stack, kMaxStack);
char** strings = backtrace_symbols(stack, size);
if (size > kStackStart) {  // Skip StackTrace c'tor and generateNew
for (std::size_t i = kStackStart; i < size; ++i) {
  char* mangName = nullptr;
  char* hex = nullptr;
  char* addr = nullptr;
  for (char* c = strings[i]; *c; ++c) {
    switch (*c) {
    case '(':
      mangName = c;
      break;
    case '+':
      hex = c;
      break;
    case ')':
      addr = c;
      break;
    default:
      break;
    }
  }
  // Perform demangling if parsed properly
  if (mangName != nullptr && hex != nullptr && addr != nullptr && mangName < hex) {
    *mangName++ = '\0';
    *hex++ = '\0';
    *addr++ = '\0';
    int status = 0;
    char* demangName = abi::__cxa_demangle(mangName, 0, 0, &status);
    // if demangling is successful, output the demangled function name
    if (status == 0) {
      // Success (see http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html)
      StackTraceEntry entry(i - 1, strings[i], demangName, hex, addr);
      m_stack.push_back(entry);
    } else {
      // Not successful - we will use mangled name
      StackTraceEntry entry(i - 1, strings[i], mangName, hex, addr);
      m_stack.push_back(entry);
    }
    free(demangName);
  } else {
    StackTraceEntry entry(i - 1, strings[i]);
    m_stack.push_back(entry);
  }
}
}
free(strings);
#else
ELPP_INTERNAL_INFO(1, "Stacktrace generation not supported for selected compiler");
#endif  // ELPP_STACKTRACE
}

// Static helper functions

static std::string crashReason(int sig) {
std::stringstream ss;
bool foundReason = false;
for (int i = 0; i < base::consts::kCrashSignalsCount; ++i) {
if (base::consts::kCrashSignals[i].numb == sig) {
  ss << "Application has crashed due to [" << base::consts::kCrashSignals[i].name << "] signal";
  if (ELPP->hasFlag(el::LoggingFlag::LogDetailedCrashReason)) {
    ss << std::endl <<
       "    " << base::consts::kCrashSignals[i].brief << std::endl <<
       "    " << base::consts::kCrashSignals[i].detail;
  }
  foundReason = true;
}
}
if (!foundReason) {
ss << "Application has crashed due to unknown signal [" << sig << "]";
}
return ss.str();
}
/// @brief Logs reason of crash from sig
static void logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) {
std::stringstream ss;
ss << "CRASH HANDLED; ";
ss << crashReason(sig);
#if ELPP_STACKTRACE
if (stackTraceIfAvailable) {
ss << std::endl << "    ======= Backtrace: =========" << std::endl << base::debug::StackTrace();
}
#else
ELPP_UNUSED(stackTraceIfAvailable);
#endif  // ELPP_STACKTRACE
ELPP_WRITE_LOG(el::base::Writer, level, base::DispatchAction::NormalLog, logger) << ss.str();
}

static inline void crashAbort(int sig) {
base::utils::abort(sig, std::string());
}

/// @brief Default application crash handler
///
/// @detail This function writes log using 'default' logger, prints stack trace for GCC based compilers and aborts program.
static inline void defaultCrashHandler(int sig) {
base::debug::logCrashReason(sig, true, Level::Fatal, base::consts::kDefaultLoggerId);
base::debug::crashAbort(sig);
}

// CrashHandler

CrashHandler::CrashHandler(bool useDefault) {
if (useDefault) {
setHandler(defaultCrashHandler);
}
}

void CrashHandler::setHandler(const Handler& cHandler) {
m_handler = cHandler;
#if defined(ELPP_HANDLE_SIGABRT)
int i = 0;  // SIGABRT is at base::consts::kCrashSignals[0]
#else
int i = 1;
#endif  // defined(ELPP_HANDLE_SIGABRT)
for (; i < base::consts::kCrashSignalsCount; ++i) {
m_handler = signal(base::consts::kCrashSignals[i].numb, cHandler);
}
}

#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG)
    }  // namespace debug
} // namespace base

// el

// Helpers

#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG)

void Helpers::crashAbort(int sig, const char* sourceFile, unsigned int long line) {
std::stringstream ss;
ss << base::debug::crashReason(sig).c_str();
ss << " - [Called el::Helpers::crashAbort(" << sig << ")]";
if (sourceFile != nullptr && strlen(sourceFile) > 0) {
ss << " - Source: " << sourceFile;
if (line > 0)
  ss << ":" << line;
else
  ss << " (line number not specified)";
}
base::utils::abort(sig, ss.str());
}

void Helpers::logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) {
el::base::debug::logCrashReason(sig, stackTraceIfAvailable, level, logger);
}

#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG)

// Loggers

Logger *Loggers::getLogger(const std::string &identity, bool registerIfNotAvailable) {
    base::threading::ScopedLock scopedLock(ELPP->lock());
    return ELPP->registeredLoggers()->get(identity, registerIfNotAvailable);
}

void Loggers::setDefaultLogBuilder(el::LogBuilderPtr &logBuilderPtr) {
    ELPP->registeredLoggers()->setDefaultLogBuilder(logBuilderPtr);
}

bool Loggers::unregisterLogger(const std::string &identity) {
    base::threading::ScopedLock scopedLock(ELPP->lock());
    return ELPP->registeredLoggers()->remove(identity);
}

bool Loggers::hasLogger(const std::string &identity) {
    base::threading::ScopedLock scopedLock(ELPP->lock());
    return ELPP->registeredLoggers()->has(identity);
}

Logger *Loggers::reconfigureLogger(Logger *logger, const Configurations &configurations) {
    if (!logger) return nullptr;
    logger->configure(configurations);
    return logger;
}

Logger *Loggers::reconfigureLogger(const std::string &identity, const Configurations &configurations) {
    return Loggers::reconfigureLogger(Loggers::getLogger(identity), configurations);
}

Logger *Loggers::reconfigureLogger(const std::string &identity, ConfigurationType configurationType,
                                   const std::string &value) {
    Logger *logger = Loggers::getLogger(identity);
    if (logger == nullptr) {
        return nullptr;
    }
    logger->configurations()->set(Level::Global, configurationType, value);
    logger->reconfigure();
    return logger;
}

void Loggers::reconfigureAllLoggers(const Configurations &configurations) {
    for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin();
         it != ELPP->registeredLoggers()->end(); ++it) {
        Loggers::reconfigureLogger(it->second, configurations);
    }
}

void Loggers::reconfigureAllLoggers(Level level, ConfigurationType configurationType,
                                    const std::string &value) {
    for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin();
         it != ELPP->registeredLoggers()->end(); ++it) {
        Logger *logger = it->second;
        logger->configurations()->set(level, configurationType, value);
        logger->reconfigure();
    }
}

void Loggers::setDefaultConfigurations(const Configurations &configurations, bool reconfigureExistingLoggers) {
    ELPP->registeredLoggers()->setDefaultConfigurations(configurations);
    if (reconfigureExistingLoggers) {
        Loggers::reconfigureAllLoggers(configurations);
    }
}

const Configurations *Loggers::defaultConfigurations(void) {
    return ELPP->registeredLoggers()->defaultConfigurations();
}

const base::LogStreamsReferenceMap *Loggers::logStreamsReference(void) {
    return ELPP->registeredLoggers()->logStreamsReference();
}

base::TypedConfigurations Loggers::defaultTypedConfigurations(void) {
    return base::TypedConfigurations(
            ELPP->registeredLoggers()->defaultConfigurations(),
            ELPP->registeredLoggers()->logStreamsReference());
}

std::vector<std::string> *Loggers::populateAllLoggerIds(std::vector<std::string> *targetList) {
    targetList->clear();
    for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->list().begin();
         it != ELPP->registeredLoggers()->list().end(); ++it) {
        targetList->push_back(it->first);
    }
    return targetList;
}

void Loggers::configureFromGlobal(const char *globalConfigurationFilePath) {
    std::ifstream gcfStream(globalConfigurationFilePath, std::ifstream::in);
    ELPP_ASSERT(gcfStream.is_open(), "Unable to open global configuration file [" << globalConfigurationFilePath
                                                                                  << "] for parsing.");
    std::string line = std::string();
    std::stringstream ss;
    Logger *logger = nullptr;
    auto configure = [&](void) {
        ELPP_INTERNAL_INFO(8, "Configuring logger: '" << logger->id() << "' with configurations \n" << ss.str()
                                                      << "\n--------------");
        Configurations c;
        c.parseFromText(ss.str());
        logger->configure(c);
    };
    while (gcfStream.good()) {
        std::getline(gcfStream, line);
        ELPP_INTERNAL_INFO(1, "Parsing line: " << line);
        base::utils::Str::trim(line);
        if (Configurations::Parser::isComment(line)) continue;
        Configurations::Parser::ignoreComments(&line);
        base::utils::Str::trim(line);
        if (line.size() > 2 && base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLoggerId))) {
            if (!ss.str().empty() && logger != nullptr) {
                configure();
            }
            ss.str(std::string(""));
            line = line.substr(2);
            base::utils::Str::trim(line);
            if (line.size() > 1) {
                ELPP_INTERNAL_INFO(1, "Getting logger: '" << line << "'");
                logger = getLogger(line);
            }
        } else {
            ss << line << "\n";
        }
    }
    if (!ss.str().empty() && logger != nullptr) {
        configure();
    }
}

bool Loggers::configureFromArg(const char *argKey) {
#if defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS)
    ELPP_UNUSED(argKey);
#else
    if (!Helpers::commandLineArgs()->hasParamWithValue(argKey)) {
        return false;
    }
    configureFromGlobal(Helpers::commandLineArgs()->getParamValue(argKey));
#endif  // defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS)
    return true;
}

void Loggers::flushAll(void) {
    ELPP->registeredLoggers()->flushAll();
}

void Loggers::setVerboseLevel(base::type::VerboseLevel level) {
    ELPP->vRegistry()->setLevel(level);
}

base::type::VerboseLevel Loggers::verboseLevel(void) {
    return ELPP->vRegistry()->level();
}

void Loggers::setVModules(const char *modules) {
    if (ELPP->vRegistry()->vModulesEnabled()) {
        ELPP->vRegistry()->setModules(modules);
    }
}

void Loggers::clearVModules(void) {
    ELPP->vRegistry()->clearModules();
}

// VersionInfo

const std::string VersionInfo::version(void) {
    return std::string("9.95.0");
}

/// @brief Release date of current version
const std::string VersionInfo::releaseDate(void) {
    return std::string("02-08-2017 2312hrs");
}

} // namespace el